From bc351ce9a3578ef87d6cf9b78fdc317cbc1d32bc Mon Sep 17 00:00:00 2001 From: Zach Robin Date: Tue, 5 Dec 2023 11:37:55 -0800 Subject: [PATCH 01/36] Added in progress button --- .../InProgressFormButton.js | 32 +++++++++++++++++ .../InProgressFormButtonStyle.css | 10 ++++++ src/components/RequestBox/RequestBox.js | 36 +++---------------- 3 files changed, 47 insertions(+), 31 deletions(-) create mode 100644 src/components/RequestBox/InProgressFormButton/InProgressFormButton.js create mode 100644 src/components/RequestBox/InProgressFormButton/InProgressFormButtonStyle.css diff --git a/src/components/RequestBox/InProgressFormButton/InProgressFormButton.js b/src/components/RequestBox/InProgressFormButton/InProgressFormButton.js new file mode 100644 index 00000000..e93dff8e --- /dev/null +++ b/src/components/RequestBox/InProgressFormButton/InProgressFormButton.js @@ -0,0 +1,32 @@ +import { Box, Button, Paper, Typography, ButtonGroup } from '@mui/material'; +import React from 'react'; +import './InProgressFormButtonStyle.css'; + +export default function InProgressFormButton(props) { + return ( + + {props.qrResponse.questionnaire ? ( + + + In Progress Form + + + Author: {props.qrResponse.author ? props.qrResponse.author.reference : 'empty'} + + + Last Edited: {props.qrResponse.authored ? props.qrResponse.authored : 'empty'} + + + Form Link: {props.qrResponse.questionnaire ? props.qrResponse.questionnaire : 'empty'} + + + + + + ) :

Error: No in progress forn found

+ } +
+ ); +} \ No newline at end of file diff --git a/src/components/RequestBox/InProgressFormButton/InProgressFormButtonStyle.css b/src/components/RequestBox/InProgressFormButton/InProgressFormButtonStyle.css new file mode 100644 index 00000000..461290f3 --- /dev/null +++ b/src/components/RequestBox/InProgressFormButton/InProgressFormButtonStyle.css @@ -0,0 +1,10 @@ +.inprogress-container { + background-color:#F3F6F9; + border: 1px solid #E5EAF2; + border-radius: 10px; + padding:25px; + margin:20px 0 20px 0; + + /* This should be inherited, need to change */ + width:48vw; +} \ No newline at end of file diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index ea995e28..0b0b7d2d 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -7,6 +7,7 @@ import { defaultValues, shortNameMap, types } from '../../util/data'; import { getAge } from '../../util/fhir'; import { retrieveLaunchContext } from '../../util/util'; import './request.css'; +import InProgressFormButton from './InProgressFormButton/InProgressFormButton.js'; import PatientSearchBar from './PatientSearchBar/PatientSearchBar.js'; @@ -201,7 +202,6 @@ export default class RequestBox extends Component { State: {this.state.patientState ? this.state.patientState : this.emptyField} {this.renderOtherInfo()} - {this.renderQRInfo()} ); } @@ -225,32 +225,6 @@ export default class RequestBox extends Component { ); } - renderQRInfo() { - const qrResponse = this.state.response; - return ( -
- {qrResponse.questionnaire ? ( - <> -
- In Progress Form -
-
- Form: {qrResponse.questionnaire ? qrResponse.questionnaire : this.emptyField} -
-
- Author: {qrResponse.author ? qrResponse.author.reference : this.emptyField} -
-
- Date: {qrResponse.authored ? qrResponse.authored : this.emptyField} -
- - ) : ( -
- )} -
- ); - } - renderPrefetchedResources() { const prefetchMap = new Map(Object.entries(this.state.prefetchedResources)); if (prefetchMap.size > 0) { @@ -460,7 +434,6 @@ export default class RequestBox extends Component { render() { const disableSendToCRD = this.isOrderNotSelected() || this.props.loading; - const disableLaunchDTR = !this.state.response.questionnaire; const disableSendRx = this.isOrderNotSelected() || this.props.loading; const disableLaunchSmartOnFhir = this.isPatientNotSelected(); return ( @@ -512,10 +485,11 @@ export default class RequestBox extends Component {
{this.state.patient.id ? (
+ - From 4dd6fb568f963651b68bb3d282c7a32a10f42a26 Mon Sep 17 00:00:00 2001 From: Zach Robin Date: Fri, 8 Dec 2023 07:29:47 -0800 Subject: [PATCH 02/36] addressed comments, bug fixes --- .../InProgressFormButton.js | 27 +++++++++---------- .../InProgressFormButtonStyle.css | 8 +++--- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/components/RequestBox/InProgressFormButton/InProgressFormButton.js b/src/components/RequestBox/InProgressFormButton/InProgressFormButton.js index e93dff8e..66a58833 100644 --- a/src/components/RequestBox/InProgressFormButton/InProgressFormButton.js +++ b/src/components/RequestBox/InProgressFormButton/InProgressFormButton.js @@ -4,29 +4,26 @@ import './InProgressFormButtonStyle.css'; export default function InProgressFormButton(props) { return ( - - {props.qrResponse.questionnaire ? ( - - + props.qrResponse.questionnaire ? ( + + In Progress Form - - Author: {props.qrResponse.author ? props.qrResponse.author.reference : 'empty'} + + Practitioner: {props.qrResponse.author ? props.qrResponse.author.reference : 'empty'} - - Last Edited: {props.qrResponse.authored ? props.qrResponse.authored : 'empty'} + + Last Edited: {props.qrResponse.authored ? props.qrResponse.authored : 'empty'} - - Form Link: {props.qrResponse.questionnaire ? props.qrResponse.questionnaire : 'empty'} + + Form Link: {props.qrResponse.questionnaire ? props.qrResponse.questionnaire : 'empty'} - - - ) :

Error: No in progress forn found

- } -
+ ) : '' ); } \ No newline at end of file diff --git a/src/components/RequestBox/InProgressFormButton/InProgressFormButtonStyle.css b/src/components/RequestBox/InProgressFormButton/InProgressFormButtonStyle.css index 461290f3..ae197b52 100644 --- a/src/components/RequestBox/InProgressFormButton/InProgressFormButtonStyle.css +++ b/src/components/RequestBox/InProgressFormButton/InProgressFormButtonStyle.css @@ -1,10 +1,10 @@ .inprogress-container { background-color:#F3F6F9; - border: 1px solid #E5EAF2; - border-radius: 10px; - padding:25px; + border: 1px solid black; + border-radius: 5px; + padding:20px; margin:20px 0 20px 0; /* This should be inherited, need to change */ - width:48vw; + width:48.5vw; } \ No newline at end of file From ed3104623c15d5bbac43c6d8ee508fab71af2fd0 Mon Sep 17 00:00:00 2001 From: Zach Robin Date: Fri, 8 Dec 2023 07:32:34 -0800 Subject: [PATCH 03/36] improved component name --- .../InProgressFormBox.js} | 4 ++-- .../InProgressFormBoxStyle.css} | 0 src/components/RequestBox/RequestBox.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/components/RequestBox/{InProgressFormButton/InProgressFormButton.js => InProgressFormBox/InProgressFormBox.js} (93%) rename src/components/RequestBox/{InProgressFormButton/InProgressFormButtonStyle.css => InProgressFormBox/InProgressFormBoxStyle.css} (100%) diff --git a/src/components/RequestBox/InProgressFormButton/InProgressFormButton.js b/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js similarity index 93% rename from src/components/RequestBox/InProgressFormButton/InProgressFormButton.js rename to src/components/RequestBox/InProgressFormBox/InProgressFormBox.js index 66a58833..7cfe2573 100644 --- a/src/components/RequestBox/InProgressFormButton/InProgressFormButton.js +++ b/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js @@ -1,8 +1,8 @@ import { Box, Button, Paper, Typography, ButtonGroup } from '@mui/material'; import React from 'react'; -import './InProgressFormButtonStyle.css'; +import './InProgressFormBoxStyle.css'; -export default function InProgressFormButton(props) { +export default function InProgressFormBox(props) { return ( props.qrResponse.questionnaire ? ( diff --git a/src/components/RequestBox/InProgressFormButton/InProgressFormButtonStyle.css b/src/components/RequestBox/InProgressFormBox/InProgressFormBoxStyle.css similarity index 100% rename from src/components/RequestBox/InProgressFormButton/InProgressFormButtonStyle.css rename to src/components/RequestBox/InProgressFormBox/InProgressFormBoxStyle.css diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index 0b0b7d2d..35deb168 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -7,7 +7,7 @@ import { defaultValues, shortNameMap, types } from '../../util/data'; import { getAge } from '../../util/fhir'; import { retrieveLaunchContext } from '../../util/util'; import './request.css'; -import InProgressFormButton from './InProgressFormButton/InProgressFormButton.js'; +import InProgressFormBox from './InProgressFormBox/InProgressFormBox.js'; import PatientSearchBar from './PatientSearchBar/PatientSearchBar.js'; @@ -485,7 +485,7 @@ export default class RequestBox extends Component {
{this.state.patient.id ? (
- From 627f431a56fda352c4d4258aee57e08800385a4b Mon Sep 17 00:00:00 2001 From: Zach Robin Date: Fri, 8 Dec 2023 10:26:17 -0800 Subject: [PATCH 04/36] updated background color --- .../RequestBox/InProgressFormBox/InProgressFormBoxStyle.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/RequestBox/InProgressFormBox/InProgressFormBoxStyle.css b/src/components/RequestBox/InProgressFormBox/InProgressFormBoxStyle.css index ae197b52..4e8b3eea 100644 --- a/src/components/RequestBox/InProgressFormBox/InProgressFormBoxStyle.css +++ b/src/components/RequestBox/InProgressFormBox/InProgressFormBoxStyle.css @@ -1,5 +1,5 @@ .inprogress-container { - background-color:#F3F6F9; + background-color:#f8f8f8; border: 1px solid black; border-radius: 5px; padding:20px; From 75b0226ced9b7e21c80d4a2680848b66057968fd Mon Sep 17 00:00:00 2001 From: Zach Robin Date: Fri, 8 Dec 2023 11:33:31 -0800 Subject: [PATCH 05/36] shortened main box --- src/components/RequestBox/request.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/RequestBox/request.css b/src/components/RequestBox/request.css index 4bac5323..2da628c9 100644 --- a/src/components/RequestBox/request.css +++ b/src/components/RequestBox/request.css @@ -12,7 +12,7 @@ .request { border: 1px solid black; - height:530px; + height:375px; padding: 10px; border-radius: 5px; background-color: rgb(248, 248, 248) From f72be309040aa0b21b01aded296fb73b4c6e82c0 Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Tue, 12 Dec 2023 14:10:54 -0500 Subject: [PATCH 06/36] Create guide on launching SMART on FHIR apps from Meld sandbox --- How-To-Launch-SMART-on-FHIR-Apps.md | 64 +++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 How-To-Launch-SMART-on-FHIR-Apps.md diff --git a/How-To-Launch-SMART-on-FHIR-Apps.md b/How-To-Launch-SMART-on-FHIR-Apps.md new file mode 100644 index 00000000..0c5cef43 --- /dev/null +++ b/How-To-Launch-SMART-on-FHIR-Apps.md @@ -0,0 +1,64 @@ +# How to launch external SMART on FHIR apps via Meld Environment from mcode/request-generator + +## Setup + +### In mcode/rems-admin + +1. Overwrite `SMART_ENDPOINT` in the `.env`. Overwriting environment variables in a `.env.local` does not work (this is a bug). + +```.env +SMART_ENDPOINT = https://smartlauncher.interop.community/sample-app/launch?client_id=sampleapp&platform=meld +``` + +2. Replace the `appContext` returned by the `src/hooks/rems.ordersign.ts` hook's `createSmartLink` function with a short string, e.g. `"hello"`. Otherwise, when you try to load the QuestionnaireForm from the link on any of the returned hook cards, you will run into this bug: + +``` +HttpError: 500 Server Error +URL: https://iol2auth.interop.community/token +server_error: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.DatabaseException +Internal Exception: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column 'value' at row 1 +Error Code: 1406 +Call: INSERT INTO launch_context (name, value) VALUES (?, ?) +``` + +This is just a bug with using Meld and not something we can fix; it is because the `appContext` string is too long. + +### In mcode/request-generator + +3. Set these environment variables in your `.env.local` to overwrite the default values in the `.env`. You must be added to the REMS sandbox on Meld to log in and authenticate when running request-generator locally. + +```.env +REACT_APP_CLIENT = ed8b940e-4aaa-4209-b17d-69dfe67543b9 +REACT_APP_EHR_BASE = https://gw.interop.community/REMS/data +REACT_APP_EHR_SERVER_TO_BE_SENT_TO_REMS_ADMIN_FOR_PREFETCH = https://gw.interop.community/REMS/data +REACT_APP_EHR_SERVER = https://gw.interop.community/REMS/data +REACT_APP_SMART_LAUNCH_URL = https://smartlauncher.interop.community/sample-app/launch?client_id=sampleapp&platform=meld +``` + +#### Where to grab the environment variable values + +1. `REACT_APP_CLIENT`: This is taken from Apps > Request Generator > Settings > Registered App Details > Client Id. Request Generator refers to the registered mcode/request-generator app in your Meld sandbox. +2. `REACT_APP_EHR_BASE`, `REACT_APP_EHR_SERVER_TO_BE_SENT_TO_REMS_ADMIN_FOR_PREFETCH`, `REACT_APP_EHR_SERVER`: These are taken from your Meld sandbox's sidebar, under Settings > Sandbox > Secured FHIR Server URL. +3. `REACT_APP_SMART_LAUNCH_URL`, `SMART_ENDPOINT`: This is taken from Apps > Sample App > Settings > Registered App Details > App Launch URI\*. + +## How to run + +1. Start request-generator normally and go to `http://localhost:3000/`. Click the "Authorize" button. +2. Start rems-admin normally. +3. In request-generator, click the "Select a Patient" button. +4. Select Jon Snow (id: 130803). +5. Click the "Launch SMART on FHIR app" button. This opens the SMART on FHIR app launch page provided as values to the `REACT_APP_SMART_LAUNCH_URL` and `SMART_ENDPOINT` environment variables. +6. Click the "Authorize" button. +7. You should see the expected SMART on FHIR app launch properly. + +## Running other Meld-sandbox-registered SMART on FHIR Apps + +Log in to Meld at https://meld.interop.community/. Go to My Sandboxes > REMS > Apps to try out the other Registered Apps. The example above manually tests (1). You can try the remaining options after (2) just by changing the `REACT_APP_SMART_LAUNCH_URL` and `SMART_ENDPOINT` environment variables. + +1. Sample App - launches as long as you hardcode the `appContext` to a short string. +2. Bilirubin Risk Chart - launches without needing to modify any of the supported CDS Hooks (patient-view, order-sign) +3. CDS Hooks Sandbox - Same as (2). +4. Cuestionario - Same as (2). It launches but you probably won't be able to search for a "Task Number" without knowing one beforehand. +5. My Web App - Not launchable. ISS is incorrect even with the value gotten from Meld for `REACT_APP_EHR_BASE`. +6. Recetario - It launches, but you will be stuck on the "Logging in..." page. +7. REMS Smart App - You will be redirected to the register page. The ISS will be filled in and you will need to populate the Client Id field with the value from Apps > RENS Smart on FHIR > Settings > Registered App Details > Client Id. Click Authorize. You should be able to send CDS hooks as normal. From 348c7b7b2af68b623464d8b015dc6b6e16cc7a44 Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Tue, 12 Dec 2023 15:15:46 -0500 Subject: [PATCH 07/36] Update steps and note about the Meld-specific long appContext error --- How-To-Launch-SMART-on-FHIR-Apps.md | 45 +++++++++++++++-------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/How-To-Launch-SMART-on-FHIR-Apps.md b/How-To-Launch-SMART-on-FHIR-Apps.md index 0c5cef43..c124aeb2 100644 --- a/How-To-Launch-SMART-on-FHIR-Apps.md +++ b/How-To-Launch-SMART-on-FHIR-Apps.md @@ -4,28 +4,15 @@ ### In mcode/rems-admin -1. Overwrite `SMART_ENDPOINT` in the `.env`. Overwriting environment variables in a `.env.local` does not work (this is a bug). +Overwrite `SMART_ENDPOINT` in the `.env`. Overwriting environment variables in a `.env.local` does not work (this is a bug). ```.env SMART_ENDPOINT = https://smartlauncher.interop.community/sample-app/launch?client_id=sampleapp&platform=meld ``` -2. Replace the `appContext` returned by the `src/hooks/rems.ordersign.ts` hook's `createSmartLink` function with a short string, e.g. `"hello"`. Otherwise, when you try to load the QuestionnaireForm from the link on any of the returned hook cards, you will run into this bug: - -``` -HttpError: 500 Server Error -URL: https://iol2auth.interop.community/token -server_error: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.DatabaseException -Internal Exception: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column 'value' at row 1 -Error Code: 1406 -Call: INSERT INTO launch_context (name, value) VALUES (?, ?) -``` - -This is just a bug with using Meld and not something we can fix; it is because the `appContext` string is too long. - ### In mcode/request-generator -3. Set these environment variables in your `.env.local` to overwrite the default values in the `.env`. You must be added to the REMS sandbox on Meld to log in and authenticate when running request-generator locally. +Set these environment variables in your `.env.local` to overwrite the default values in the `.env`. You must be added to the REMS sandbox on Meld to log in and authenticate when running request-generator locally. ```.env REACT_APP_CLIENT = ed8b940e-4aaa-4209-b17d-69dfe67543b9 @@ -50,15 +37,29 @@ REACT_APP_SMART_LAUNCH_URL = https://smartlauncher.interop.community/sample-app/ 5. Click the "Launch SMART on FHIR app" button. This opens the SMART on FHIR app launch page provided as values to the `REACT_APP_SMART_LAUNCH_URL` and `SMART_ENDPOINT` environment variables. 6. Click the "Authorize" button. 7. You should see the expected SMART on FHIR app launch properly. +8. Go back to request-generator and issue an order-sign hook, and click on the "Patient Enrollment Form" button. +9. You should see the expected SMART on FHIR app launch. ## Running other Meld-sandbox-registered SMART on FHIR Apps Log in to Meld at https://meld.interop.community/. Go to My Sandboxes > REMS > Apps to try out the other Registered Apps. The example above manually tests (1). You can try the remaining options after (2) just by changing the `REACT_APP_SMART_LAUNCH_URL` and `SMART_ENDPOINT` environment variables. -1. Sample App - launches as long as you hardcode the `appContext` to a short string. -2. Bilirubin Risk Chart - launches without needing to modify any of the supported CDS Hooks (patient-view, order-sign) -3. CDS Hooks Sandbox - Same as (2). -4. Cuestionario - Same as (2). It launches but you probably won't be able to search for a "Task Number" without knowing one beforehand. -5. My Web App - Not launchable. ISS is incorrect even with the value gotten from Meld for `REACT_APP_EHR_BASE`. -6. Recetario - It launches, but you will be stuck on the "Logging in..." page. -7. REMS Smart App - You will be redirected to the register page. The ISS will be filled in and you will need to populate the Client Id field with the value from Apps > RENS Smart on FHIR > Settings > Registered App Details > Client Id. Click Authorize. You should be able to send CDS hooks as normal. +1. Sample App - launches for (7) without needing to modify any of the supported CDS Hooks. You will get the following error at (9) unless you replace the `appContext` returned by the `src/hooks/rems.ordersign.ts` hook's `createSmartLink` function with a short string, e.g. `"hello"`. + +``` +HttpError: 500 Server Error +URL: https://iol2auth.interop.community/token +server_error: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.DatabaseException +Internal Exception: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column 'value' at row 1 +Error Code: 1406 +Call: INSERT INTO launch_context (name, value) VALUES (?, ?) +``` + +This is just a bug with using Meld and not something we can fix; it is because the `appContext` string is too long. + +2. Bilirubin Risk Chart - launches for (7) and (9) without needing to modify any of the supported CDS Hooks. However, for (9) above, there will be a 500 error in the DevTools console, unless you modify the `appContext` as mentioned above. +3. CDS Hooks Sandbox - launches for (7) and (9) without needing to modify any of the supported CDS Hooks. However, you will run into 500 and 404 errors for (9) above if you do not modify the `appContext` above. Note that the order-select hook won't return any cards, but the patient-view hook does. +4. Cuestionario - launches without needing to modify any of the supported CDS Hooks. However, you probably won't be able to search for a "Task Number" without knowing one beforehand. Seems to work for both (7) and (9) above. +5. My Web App - Not launchable for (7) and (9). The ISS supplied for `REACT_APP_EHR_BASE` from the Meld sandbox seems to be incorrect. +6. Recetario - It launches for both (7) and (9), but you will be stuck on the "Logging in..." page. +7. REMS Smart App - It is launchable for (7). You will be redirected to the register page. The ISS will be filled in and you will need to populate the Client Id field with the value from Apps > RENS Smart on FHIR > Settings > Registered App Details > Client Id. Click Authorize. You should be able to send CDS hooks as normal. For (9), you will run into a few 500 errors- one is related to a token and the other is related to the launch context. Modifying the `appContext` as mentioned above will let you launch, but the CDS hooks' requests and responses will not appear in the Network tab. From 43a14f5c3e34dd106e391be063a2831b74952e35 Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Thu, 14 Dec 2023 15:57:02 -0500 Subject: [PATCH 08/36] Make guide more general --- How-To-Launch-SMART-on-FHIR-Apps.md | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/How-To-Launch-SMART-on-FHIR-Apps.md b/How-To-Launch-SMART-on-FHIR-Apps.md index c124aeb2..02824ec4 100644 --- a/How-To-Launch-SMART-on-FHIR-Apps.md +++ b/How-To-Launch-SMART-on-FHIR-Apps.md @@ -1,10 +1,10 @@ -# How to launch external SMART on FHIR apps via Meld Environment from mcode/request-generator +# How to launch external SMART on FHIR apps from mcode/request-generator ## Setup ### In mcode/rems-admin -Overwrite `SMART_ENDPOINT` in the `.env`. Overwriting environment variables in a `.env.local` does not work (this is a bug). +Overwrite `SMART_ENDPOINT` in the `.env`. Overwriting environment variables in a `.env.local` does not work (this is a bug). For example, if you are using a registered app in a Meld Sandbox, your `SMART_ENDPOINT` may look like this: ```.env SMART_ENDPOINT = https://smartlauncher.interop.community/sample-app/launch?client_id=sampleapp&platform=meld @@ -22,7 +22,7 @@ REACT_APP_EHR_SERVER = https://gw.interop.community/REMS/data REACT_APP_SMART_LAUNCH_URL = https://smartlauncher.interop.community/sample-app/launch?client_id=sampleapp&platform=meld ``` -#### Where to grab the environment variable values +### Where to grab the environment variable values if using a Meld Sandbox 1. `REACT_APP_CLIENT`: This is taken from Apps > Request Generator > Settings > Registered App Details > Client Id. Request Generator refers to the registered mcode/request-generator app in your Meld sandbox. 2. `REACT_APP_EHR_BASE`, `REACT_APP_EHR_SERVER_TO_BE_SENT_TO_REMS_ADMIN_FOR_PREFETCH`, `REACT_APP_EHR_SERVER`: These are taken from your Meld sandbox's sidebar, under Settings > Sandbox > Secured FHIR Server URL. @@ -40,26 +40,6 @@ REACT_APP_SMART_LAUNCH_URL = https://smartlauncher.interop.community/sample-app/ 8. Go back to request-generator and issue an order-sign hook, and click on the "Patient Enrollment Form" button. 9. You should see the expected SMART on FHIR app launch. -## Running other Meld-sandbox-registered SMART on FHIR Apps +## Running other Registered SMART on FHIR Apps from Meld Log in to Meld at https://meld.interop.community/. Go to My Sandboxes > REMS > Apps to try out the other Registered Apps. The example above manually tests (1). You can try the remaining options after (2) just by changing the `REACT_APP_SMART_LAUNCH_URL` and `SMART_ENDPOINT` environment variables. - -1. Sample App - launches for (7) without needing to modify any of the supported CDS Hooks. You will get the following error at (9) unless you replace the `appContext` returned by the `src/hooks/rems.ordersign.ts` hook's `createSmartLink` function with a short string, e.g. `"hello"`. - -``` -HttpError: 500 Server Error -URL: https://iol2auth.interop.community/token -server_error: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.DatabaseException -Internal Exception: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column 'value' at row 1 -Error Code: 1406 -Call: INSERT INTO launch_context (name, value) VALUES (?, ?) -``` - -This is just a bug with using Meld and not something we can fix; it is because the `appContext` string is too long. - -2. Bilirubin Risk Chart - launches for (7) and (9) without needing to modify any of the supported CDS Hooks. However, for (9) above, there will be a 500 error in the DevTools console, unless you modify the `appContext` as mentioned above. -3. CDS Hooks Sandbox - launches for (7) and (9) without needing to modify any of the supported CDS Hooks. However, you will run into 500 and 404 errors for (9) above if you do not modify the `appContext` above. Note that the order-select hook won't return any cards, but the patient-view hook does. -4. Cuestionario - launches without needing to modify any of the supported CDS Hooks. However, you probably won't be able to search for a "Task Number" without knowing one beforehand. Seems to work for both (7) and (9) above. -5. My Web App - Not launchable for (7) and (9). The ISS supplied for `REACT_APP_EHR_BASE` from the Meld sandbox seems to be incorrect. -6. Recetario - It launches for both (7) and (9), but you will be stuck on the "Logging in..." page. -7. REMS Smart App - It is launchable for (7). You will be redirected to the register page. The ISS will be filled in and you will need to populate the Client Id field with the value from Apps > RENS Smart on FHIR > Settings > Registered App Details > Client Id. Click Authorize. You should be able to send CDS hooks as normal. For (9), you will run into a few 500 errors- one is related to a token and the other is related to the launch context. Modifying the `appContext` as mentioned above will let you launch, but the CDS hooks' requests and responses will not appear in the Network tab. From 49533e96bb7e57d2efcd964dfccb71867ba9e48b Mon Sep 17 00:00:00 2001 From: Robert A Dingwell Date: Mon, 18 Dec 2023 11:29:52 -0500 Subject: [PATCH 09/36] adding pre-generated public private keys for signing and verifying json web tokens used in cds-hooks calls --- .env | 2 +- public/.well-known/jwks.json | 14 +++++ src/containers/RequestBuilder.js | 24 +++------ src/keys/crdPrivateKey.js | 6 +++ src/util/auth.js | 90 +++++++++----------------------- 5 files changed, 53 insertions(+), 83 deletions(-) create mode 100644 public/.well-known/jwks.json create mode 100644 src/keys/crdPrivateKey.js diff --git a/.env b/.env index b7107e30..661d24f2 100644 --- a/.env +++ b/.env @@ -16,7 +16,7 @@ REACT_APP_PATIENT_VIEW = rems-patient-view REACT_APP_PATIENT_FHIR_QUERY = Patient?_sort=identifier&_count=12 REACT_APP_USER = alice REACT_APP_PASSWORD = alice -REACT_APP_PUBLIC_KEYS = http://localhost:3001/public_keys +REACT_APP_PUBLIC_KEYS = http://localhost:3000/request-generator/.well-known/jwks.json REACT_APP_ALT_DRUG = true REACT_APP_LAUNCH_URL = http://localhost:4040/launch REACT_APP_SMART_LAUNCH_URL = http://localhost:4040/ diff --git a/public/.well-known/jwks.json b/public/.well-known/jwks.json new file mode 100644 index 00000000..f553fc46 --- /dev/null +++ b/public/.well-known/jwks.json @@ -0,0 +1,14 @@ +{ + "keys": [ + { + "kty": "EC", + "d": "boatWqmVCQvm8wapC7XIF33oydjzXUrb6Mwz4XclkXHCSEYtdxj345LMwFJQAvrN", + "use": "sig", + "crv": "P-384", + "kid": "zGe023HzCFfY7NPb04EGvRDP1oYsTOtLNCNjDgr66AI", + "x": "GJ1EKKadP512kbQLAhu3qftADevkhCcaOFFZi376S8dvhjZU9vxNy3wplJv_GiOr", + "y": "-0nhaXoadjGOAOuMp4ekU7ricjF6So2n57k0N-VrJ9hqA-A0PhnShrmGQdBIEKah", + "alg": "ES384" + } + ] +} \ No newline at end of file diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index adecf489..06f52f72 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -8,7 +8,7 @@ import SettingsBox from '../components/SettingsBox/SettingsBox'; import RequestBox from '../components/RequestBox/RequestBox'; import buildRequest from '../util/buildRequest.js'; import { types } from '../util/data.js'; -import { createJwt, setupKeys } from '../util/auth'; +import { createJwt } from '../util/auth'; import env from 'env-var'; import FHIR from 'fhirclient'; @@ -16,7 +16,6 @@ export default class RequestBuilder extends Component { constructor(props) { super(props); this.state = { - keypair: null, loading: false, logs: [], patient: {}, @@ -55,11 +54,6 @@ export default class RequestBuilder extends Component { } componentDidMount() { - const callback = keypair => { - this.setState({ keypair }); - }; - - setupKeys(callback); if (!this.state.client) { this.reconnectEhr(); } else { @@ -129,16 +123,12 @@ export default class RequestBuilder extends Component { this.consoleLog("ERROR: unknown hook type: '", hook, "'"); return; } - - const createHeaders = () => { - const init = { 'Content-Type': 'application/json' }; - if (this.state.generateJsonToken) { - const jwt = 'Bearer ' + createJwt(this.state.keypair, this.state.baseUrl, cdsUrl); - init.authorization = jwt; - } - return new Headers(init); - }; - + let baseUrl = this.state.baseUrl; + const jwt = 'Bearer ' + createJwt( baseUrl, cdsUrl); + const headers = new Headers({ + 'Content-Type': 'application/json', + authorization: jwt + }); try { fetch(cdsUrl, { method: 'POST', diff --git a/src/keys/crdPrivateKey.js b/src/keys/crdPrivateKey.js new file mode 100644 index 00000000..f8ad3a14 --- /dev/null +++ b/src/keys/crdPrivateKey.js @@ -0,0 +1,6 @@ +const privKey = `-----BEGIN PRIVATE KEY----- +ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDBuhq1aqZUJC+bzBqkLtcgX +fejJ2PNdStvozDPhdyWRccJIRi13GPfjkszAUlAC+s0= +-----END PRIVATE KEY-----`; + +export default privKey; \ No newline at end of file diff --git a/src/util/auth.js b/src/util/auth.js index d0559037..9dcee5ac 100644 --- a/src/util/auth.js +++ b/src/util/auth.js @@ -1,15 +1,8 @@ +import privKey from '../keys/crdPrivateKey.js'; import KJUR, { KEYUTIL } from 'jsrsasign'; +import { v4 as uuidv4 } from 'uuid'; import env from 'env-var'; -function makeid() { - var text = []; - var possible = '---ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - - for (var i = 0; i < 25; i++) - text.push(possible.charAt(Math.floor(Math.random() * possible.length))); - - return text.join(''); -} function login() { const tokenUrl = @@ -41,65 +34,32 @@ function login() { }); } -function createJwt(keypair, baseUrl, cdsUrl) { - console.log('creating jwt'); - const currentTime = KJUR.jws.IntDate.get('now'); - const endTime = KJUR.jws.IntDate.get('now + 1day'); - const kid = KJUR.jws.JWS.getJWKthumbprint(keypair.public); - - const header = { - alg: 'RS256', - typ: 'JWT', - kid: kid, - jku: env.get('REACT_APP_PUBLIC_KEYS').asString() - }; - const body = { - iss: baseUrl, - aud: cdsUrl, - iat: currentTime, - exp: endTime, - jti: makeid() - }; - - var sJWT = KJUR.jws.JWS.sign( - 'RS256', - JSON.stringify(header), - JSON.stringify(body), - keypair.private - ); - return sJWT; -} -function setupKeys(callback) { - const { prvKeyObj, pubKeyObj } = KEYUTIL.generateKeypair('RSA', 2048); - const jwkPrv2 = KEYUTIL.getJWKFromKey(prvKeyObj); - const jwkPub2 = KEYUTIL.getJWKFromKey(pubKeyObj); - const kid = KJUR.jws.JWS.getJWKthumbprint(jwkPub2); +/** + * Generates a JWT for a CDS service call, given the audience (the URL endpoint). The JWT is signed using a private key stored on the repository. + * + * Note: In production environments, the JWT should be signed on a secured server for best practice. The private key is exposed on the repository + * as it is an open source client-side project and tool. + * @param {*} audience - URL endpoint acting as the audience + */ + function createJwt(baseUrl, audience) { - const keypair = { - private: jwkPrv2, - public: jwkPub2, - kid: kid - }; + const jwtPayload = JSON.stringify({ + iss: baseUrl, + aud: audience, + exp: Math.round((Date.now() / 1000) + 300), + iat: Math.round((Date.now() / 1000)), + jti: uuidv4(), + }); - const pubPem = { - pem: jwkPub2, - id: kid - }; + const jwtHeader = JSON.stringify({ + alg: 'ES384', + typ: 'JWT', + kid: 'zGe023HzCFfY7NPb04EGvRDP1oYsTOtLNCNjDgr66AI', + jku: env.get('REACT_APP_PUBLIC_KEYS').asString() + }); - fetch(`${env.get('REACT_APP_PUBLIC_KEYS').asString()}/`, { - body: JSON.stringify(pubPem), - headers: { - 'Content-Type': 'application/json' - }, - method: 'POST' - }) - .then(response => { - callback(keypair); - }) - .catch(error => { - console.log(error); - }); + return KJUR.jws.JWS.sign(null, jwtHeader, jwtPayload, privKey); } -export { createJwt, login, setupKeys }; +export { createJwt, login }; From 2540f6f60cda7f3e3243bfa528d540a163104d42 Mon Sep 17 00:00:00 2001 From: Robert A Dingwell Date: Tue, 19 Dec 2023 10:20:00 -0500 Subject: [PATCH 10/36] Prttier fixes --- public/.well-known/jwks.json | 22 +++++++++++----------- src/.DS_Store | Bin 0 -> 6148 bytes src/containers/RequestBuilder.js | 2 +- src/keys/crdPrivateKey.js | 2 +- src/util/auth.js | 11 ++++------- 5 files changed, 17 insertions(+), 20 deletions(-) create mode 100644 src/.DS_Store diff --git a/public/.well-known/jwks.json b/public/.well-known/jwks.json index f553fc46..0465086b 100644 --- a/public/.well-known/jwks.json +++ b/public/.well-known/jwks.json @@ -1,14 +1,14 @@ { "keys": [ - { - "kty": "EC", - "d": "boatWqmVCQvm8wapC7XIF33oydjzXUrb6Mwz4XclkXHCSEYtdxj345LMwFJQAvrN", - "use": "sig", - "crv": "P-384", - "kid": "zGe023HzCFfY7NPb04EGvRDP1oYsTOtLNCNjDgr66AI", - "x": "GJ1EKKadP512kbQLAhu3qftADevkhCcaOFFZi376S8dvhjZU9vxNy3wplJv_GiOr", - "y": "-0nhaXoadjGOAOuMp4ekU7ricjF6So2n57k0N-VrJ9hqA-A0PhnShrmGQdBIEKah", - "alg": "ES384" - } + { + "kty": "EC", + "d": "boatWqmVCQvm8wapC7XIF33oydjzXUrb6Mwz4XclkXHCSEYtdxj345LMwFJQAvrN", + "use": "sig", + "crv": "P-384", + "kid": "zGe023HzCFfY7NPb04EGvRDP1oYsTOtLNCNjDgr66AI", + "x": "GJ1EKKadP512kbQLAhu3qftADevkhCcaOFFZi376S8dvhjZU9vxNy3wplJv_GiOr", + "y": "-0nhaXoadjGOAOuMp4ekU7ricjF6So2n57k0N-VrJ9hqA-A0PhnShrmGQdBIEKah", + "alg": "ES384" + } ] -} \ No newline at end of file +} diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4efe7f2488c12a7abaa0448d03911fb783443795 GIT binary patch literal 6148 zcmeHKJ5EC}5S)b+5i}_&eFbh{Md1Wo07xj1BA!H`e--D-(K7ofh+fi#CYqJjW3P8? zd5X7h0od|q^9U>e%;}E!^@1ciWrg3GhXq5PrTy`yGi!xfOGG##z5ZpU;bvd z-o6dP))Qn>Knh3!DIf);z{M1((mKDt*oitw3P^!#QNX_sjqcbB$He$_aEKOwIAb`B z^XMgr%>%?=eJ0Q^+b(QKnffy zaGT48*Z(K_ng0Knq?Htq0#~Jg%~sE=C7)Eab@Di`wT=El_nbZ5jq{*zh;mGfa?FL7 e}w-uuR literal 0 HcmV?d00001 diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index 06f52f72..8efb4d36 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -124,7 +124,7 @@ export default class RequestBuilder extends Component { return; } let baseUrl = this.state.baseUrl; - const jwt = 'Bearer ' + createJwt( baseUrl, cdsUrl); + const jwt = 'Bearer ' + createJwt(baseUrl, cdsUrl); const headers = new Headers({ 'Content-Type': 'application/json', authorization: jwt diff --git a/src/keys/crdPrivateKey.js b/src/keys/crdPrivateKey.js index f8ad3a14..10b2c799 100644 --- a/src/keys/crdPrivateKey.js +++ b/src/keys/crdPrivateKey.js @@ -3,4 +3,4 @@ ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDBuhq1aqZUJC+bzBqkLtcgX fejJ2PNdStvozDPhdyWRccJIRi13GPfjkszAUlAC+s0= -----END PRIVATE KEY-----`; -export default privKey; \ No newline at end of file +export default privKey; diff --git a/src/util/auth.js b/src/util/auth.js index 9dcee5ac..0258583b 100644 --- a/src/util/auth.js +++ b/src/util/auth.js @@ -3,7 +3,6 @@ import KJUR, { KEYUTIL } from 'jsrsasign'; import { v4 as uuidv4 } from 'uuid'; import env from 'env-var'; - function login() { const tokenUrl = env.get('REACT_APP_AUTH').asString() + @@ -34,7 +33,6 @@ function login() { }); } - /** * Generates a JWT for a CDS service call, given the audience (the URL endpoint). The JWT is signed using a private key stored on the repository. * @@ -42,14 +40,13 @@ function login() { * as it is an open source client-side project and tool. * @param {*} audience - URL endpoint acting as the audience */ - function createJwt(baseUrl, audience) { - +function createJwt(baseUrl, audience) { const jwtPayload = JSON.stringify({ iss: baseUrl, aud: audience, - exp: Math.round((Date.now() / 1000) + 300), - iat: Math.round((Date.now() / 1000)), - jti: uuidv4(), + exp: Math.round(Date.now() / 1000 + 300), + iat: Math.round(Date.now() / 1000), + jti: uuidv4() }); const jwtHeader = JSON.stringify({ From 63acae2f7f4ba18101d1233bba6d091bddc970fa Mon Sep 17 00:00:00 2001 From: Robert A Dingwell Date: Tue, 19 Dec 2023 10:42:24 -0500 Subject: [PATCH 11/36] fixing issue introduced in rebase --- src/containers/RequestBuilder.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index 8efb4d36..8976f5d3 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -123,16 +123,22 @@ export default class RequestBuilder extends Component { this.consoleLog("ERROR: unknown hook type: '", hook, "'"); return; } + let baseUrl = this.state.baseUrl; - const jwt = 'Bearer ' + createJwt(baseUrl, cdsUrl); - const headers = new Headers({ + + const headers = { 'Content-Type': 'application/json', authorization: jwt - }); + }; + if (this.state.generateJsonToken) { + const jwt = 'Bearer ' + createJwt(baseUrl, cdsUrl); + headers.authorization = jwt; + } + try { fetch(cdsUrl, { method: 'POST', - headers: createHeaders(), + headers: new Headers(headers), body: JSON.stringify(json_request), signal: this.timeout(10).signal //Timeout set to 10 seconds }) @@ -220,7 +226,7 @@ export default class RequestBuilder extends Component { ref={this.requestBox} loading={this.state.loading} consoleLog={this.consoleLog} - patientFhirQuery ={this.state.patientFhirQuery} + patientFhirQuery={this.state.patientFhirQuery} />

From 9c0a70466dca2cab2b91a1aa860c2376313857dd Mon Sep 17 00:00:00 2001 From: Robert A Dingwell Date: Tue, 19 Dec 2023 10:50:03 -0500 Subject: [PATCH 12/36] fixing undefined var issue --- src/containers/RequestBuilder.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index 8976f5d3..777538cd 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -127,8 +127,7 @@ export default class RequestBuilder extends Component { let baseUrl = this.state.baseUrl; const headers = { - 'Content-Type': 'application/json', - authorization: jwt + 'Content-Type': 'application/json' }; if (this.state.generateJsonToken) { const jwt = 'Bearer ' + createJwt(baseUrl, cdsUrl); From d71d5ec09cde45371f53c7600d93b3a7a5265c69 Mon Sep 17 00:00:00 2001 From: Ariel Virgulto Date: Thu, 21 Dec 2023 08:32:10 -0600 Subject: [PATCH 13/36] Migrate to accordian, update styling/functionality --- .../PatientSearchBar/PatientSearchBar.js | 8 +- src/components/RequestBox/RequestBox.js | 87 +++++++++++-------- src/components/RequestBox/request.css | 6 +- src/components/SMARTBox/PatientBox.js | 2 + 4 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js b/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js index 123ed142..3e1a38bf 100644 --- a/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js +++ b/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js @@ -57,7 +57,7 @@ export default function PatientSearchBar(props) { {filteredListOfPatients.map(patient => { return ( -
+ item.id === patient.id)} @@ -72,7 +72,7 @@ export default function PatientSearchBar(props) { responseExpirationDays={props.responseExpirationDays} defaultUser={props.defaultUser} /> -
+ ); })}
@@ -80,8 +80,8 @@ export default function PatientSearchBar(props) { } return ( -
+ {listOfPatients[0] ? patientSearchBar() : 'loading...'} -
+ ); } \ No newline at end of file diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index 35deb168..da62d35b 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -8,31 +8,36 @@ import { getAge } from '../../util/fhir'; import { retrieveLaunchContext } from '../../util/util'; import './request.css'; import InProgressFormBox from './InProgressFormBox/InProgressFormBox.js'; +import Accordion from '@mui/material/Accordion'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import PatientSearchBar from './PatientSearchBar/PatientSearchBar.js'; -const style = { - position: 'absolute', - top: '50%', - left: '50%', - flexDirection: 'column', - width: '80%', - height: '70%', - overflowY: 'scroll', - transform: 'translate(-50%, -50%)', - display: 'flex', - bgcolor: 'background.paper', - border: '2px solid #000', - borderBottom: '2px solid black', - boxShadow: 24, - p: 4, - padding: '50px' -}; +// const style = { +// position: 'absolute', +// top: '50%', +// left: '50%', +// flexDirection: 'column', +// width: '80%', +// height: '70%', +// overflowY: 'scroll', +// transform: 'translate(-50%, -50%)', +// display: 'flex', +// bgcolor: 'background.paper', +// border: '2px solid #000', +// borderBottom: '2px solid black', +// boxShadow: 24, +// p: 4, +// padding: '50px' +// }; export default class RequestBox extends Component { constructor(props) { super(props); this.state = { openPatient: false, + expanded: false, patientList: [], patient: {}, prefetchedResources: new Map(), @@ -157,13 +162,16 @@ export default class RequestBox extends Component { getPatients = () => { + console.log('getting patients -- > ', this.props.patientFhirQuery); this.props.client .request(this.props.patientFhirQuery, { flat: true }) .then(result => { + console.log('result is -- > ', result); this.setState({ patientList: result, - openPatient: true + // openPatient: true, + expanded: true }); }) .catch(e => { @@ -432,6 +440,13 @@ export default class RequestBox extends Component { return Object.keys(this.state.patient).length === 0; } + handleChange = (panel) => (event, isExpanded) => { + if (isExpanded) { + this.getPatients(); + } + this.setState({ expanded: isExpanded ? true: false}); + }; + render() { const disableSendToCRD = this.isOrderNotSelected() || this.props.loading; const disableSendRx = this.isOrderNotSelected() || this.props.loading; @@ -439,15 +454,19 @@ export default class RequestBox extends Component { return (
- - + } + aria-controls="panel1a-content" + id="panel1a-header" > - {/* Patient selection pop up and search */} - + + + +
+ {this.state.patientList instanceof Error ? this.renderError() : } - -
- +
+ + +
{this.state.patient.id ? ( Patient ID: {this.state.patient.id} @@ -477,13 +495,12 @@ export default class RequestBox extends Component { No patient selected )}
-
+
{this.renderPatientInfo()} {this.renderPrefetchedResources()}
-
- {this.state.patient.id ? ( + {this.state.patient.id ? (
)} +
+
); } diff --git a/src/components/RequestBox/request.css b/src/components/RequestBox/request.css index 2da628c9..c0a3d539 100644 --- a/src/components/RequestBox/request.css +++ b/src/components/RequestBox/request.css @@ -12,7 +12,7 @@ .request { border: 1px solid black; - height:375px; + height: auto; padding: 10px; border-radius: 5px; background-color: rgb(248, 248, 248) @@ -123,4 +123,8 @@ .empty-field { color: dimgrey; font-style: italic; +} + +.patient-info { + margin: 5px; } \ No newline at end of file diff --git a/src/components/SMARTBox/PatientBox.js b/src/components/SMARTBox/PatientBox.js index af8001ff..509aa7a0 100644 --- a/src/components/SMARTBox/PatientBox.js +++ b/src/components/SMARTBox/PatientBox.js @@ -140,6 +140,7 @@ export default class PatientBox extends Component { const response = JSON.parse(this.state.response); this.updateQRResponse(patient, response); } + this.props.callback('expanded', false); } updateQRResponse(patient, response) { @@ -389,6 +390,7 @@ export default class PatientBox extends Component { render() { const patient = this.props.patient; + console.log('patient --- > ', patient); let name = ''; if (patient.name) { name = {`${patient.name[0].given[0]} ${patient.name[0].family}`} ; From b68d25ec4b240d1e9080a075c070e3ad915ab759 Mon Sep 17 00:00:00 2001 From: Ariel Virgulto Date: Thu, 21 Dec 2023 09:17:50 -0600 Subject: [PATCH 14/36] Clear out state before new query --- .../InProgressFormBoxStyle.css | 3 --- src/components/RequestBox/RequestBox.js | 21 +++++++------------ src/components/SMARTBox/PatientBox.js | 4 +--- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/components/RequestBox/InProgressFormBox/InProgressFormBoxStyle.css b/src/components/RequestBox/InProgressFormBox/InProgressFormBoxStyle.css index 4e8b3eea..ceea9016 100644 --- a/src/components/RequestBox/InProgressFormBox/InProgressFormBoxStyle.css +++ b/src/components/RequestBox/InProgressFormBox/InProgressFormBoxStyle.css @@ -4,7 +4,4 @@ border-radius: 5px; padding:20px; margin:20px 0 20px 0; - - /* This should be inherited, need to change */ - width:48.5vw; } \ No newline at end of file diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index da62d35b..63bb9b29 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -36,7 +36,6 @@ export default class RequestBox extends Component { constructor(props) { super(props); this.state = { - openPatient: false, expanded: false, patientList: [], patient: {}, @@ -68,10 +67,6 @@ export default class RequestBox extends Component { componentDidMount() { } - exitSmart = () => { - this.setState({ openPatient: false }); - }; - prepPrefetch() { const preppedResources = new Map(); Object.keys(this.state.prefetchedResources).forEach(resourceKey => { @@ -161,16 +156,11 @@ export default class RequestBox extends Component { }; getPatients = () => { - - console.log('getting patients -- > ', this.props.patientFhirQuery); - this.props.client .request(this.props.patientFhirQuery, { flat: true }) .then(result => { - console.log('result is -- > ', result); this.setState({ patientList: result, - // openPatient: true, expanded: true }); }) @@ -440,9 +430,14 @@ export default class RequestBox extends Component { return Object.keys(this.state.patient).length === 0; } - handleChange = (panel) => (event, isExpanded) => { + handleChange = () => (event, isExpanded) => { if (isExpanded) { this.getPatients(); + } else { + this.setState({ + patientList: [], + expanded: false + }); } this.setState({ expanded: isExpanded ? true: false}); }; @@ -454,7 +449,7 @@ export default class RequestBox extends Component { return (
- + } aria-controls="panel1a-content" @@ -483,7 +478,7 @@ export default class RequestBox extends Component { responseExpirationDays={this.props.responseExpirationDays} defaultUser={this.props.defaultUser} />} - +
diff --git a/src/components/SMARTBox/PatientBox.js b/src/components/SMARTBox/PatientBox.js index 509aa7a0..b95d49c1 100644 --- a/src/components/SMARTBox/PatientBox.js +++ b/src/components/SMARTBox/PatientBox.js @@ -118,7 +118,7 @@ export default class PatientBox extends Component { updateValues(patient) { this.props.callback('patient', patient); - this.props.callback('openPatient', false); + this.props.callback('expanded', false); this.props.clearCallback(); if (this.state.request) { const request = JSON.parse(this.state.request); @@ -140,7 +140,6 @@ export default class PatientBox extends Component { const response = JSON.parse(this.state.response); this.updateQRResponse(patient, response); } - this.props.callback('expanded', false); } updateQRResponse(patient, response) { @@ -324,7 +323,6 @@ export default class PatientBox extends Component { } getRequests() { - console.log(this.props.client); const patientId = this.props.patient.id; this.getDeviceRequest(patientId); this.getServiceRequest(patientId); From 2e7a24bcb128f828492dfc2a7d64c67134cf808c Mon Sep 17 00:00:00 2001 From: rdingwell Date: Fri, 22 Dec 2023 11:11:47 -0500 Subject: [PATCH 15/36] Update README.md Adding description for public/private keys --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 04e21b68..830d0c66 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ This should open a browser window directed to the value set in `REACT_APP_URL`. ## Versions This application requires node v14. +## Keys +Embedded in the application are the public and provate keys used to generate and verify JSON Web Tokens (JWT) that are used to authenticate/authorize calls to a CDS-Hooks service. The public key is contained in the public/.well-known/jwks.json document. The private key is contained in src/keys/crdPrivateKey.js file. The keys were generated from https://mkjwk.org/. To update these keys you can generate a new key pair from this site, ensure that you request the Show X.509 option is set to yes. Once generated you can replace the public and private keys. You will also need to update the src/utils/auth.js file with the corrisponding key information. ### How To Override Defaults The .env file contains the default URI paths, these can be overwritten from the start command as follows: @@ -52,4 +54,4 @@ Following are a list of modifiable paths: | HTTPS | `false` | | HTTPS_KEY_PATH | `server.key` | | HTTPS_CERT_PATH | `server.cert` | -| REACT_APP_PATIENT_FHIR_QUERY | `Patient?_sort=identifier&_count=12` | \ No newline at end of file +| REACT_APP_PATIENT_FHIR_QUERY | `Patient?_sort=identifier&_count=12` | From b1920b9042bcbc6d1a8a9632a22eba70630d5867 Mon Sep 17 00:00:00 2001 From: Ariel Virgulto Date: Tue, 26 Dec 2023 09:50:25 -0600 Subject: [PATCH 16/36] Update workflow of select a patient and update call of patients --- .../PatientSearchBar/PatientSearchBar.js | 38 ++- .../PatientSearchBarStyle.css | 11 +- src/components/RequestBox/RequestBox.js | 257 +++++------------- src/components/RequestBox/request.css | 2 +- src/components/SMARTBox/PatientBox.js | 5 +- src/containers/RequestBuilder.js | 134 ++++++++- src/index.css | 13 +- 7 files changed, 235 insertions(+), 225 deletions(-) diff --git a/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js b/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js index 3e1a38bf..250428f4 100644 --- a/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js +++ b/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js @@ -24,20 +24,37 @@ export default function PatientSearchBar(props) { } return ''; } + + function getFilteredLength(searchstring, listOfPatients) { + const filteredListOfPatients = listOfPatients[0].filter((element) => { + if (searchstring === '') { + return element; + } + else { + return element.name.toLowerCase().includes(searchstring); + } + }); + + return filteredListOfPatients.length; + } function patientSearchBar() { return ( - { - setInput(newInputValue.toLowerCase()); - }} - options={listOfPatients[0].map(item => item.name)} - renderInput={(params) => } /> + +

Filter patient list

+ { + setInput(newInputValue.toLowerCase()); + }} + options={listOfPatients[0].map(item => item.name)} + renderInput={(params) => } /> +

Showing {getFilteredLength(input, listOfPatients)} of {props.searchablePatients.length} records

+
{displayFilteredPatientList(input, listOfPatients[0])}
); @@ -52,7 +69,6 @@ export default function PatientSearchBar(props) { return element.name.toLowerCase().includes(searchstring); } }); - return ( {filteredListOfPatients.map(patient => { diff --git a/src/components/RequestBox/PatientSearchBar/PatientSearchBarStyle.css b/src/components/RequestBox/PatientSearchBar/PatientSearchBarStyle.css index bcd1cac6..4908c658 100644 --- a/src/components/RequestBox/PatientSearchBar/PatientSearchBarStyle.css +++ b/src/components/RequestBox/PatientSearchBar/PatientSearchBarStyle.css @@ -3,10 +3,8 @@ } .search-box { - top: -10; - width: 100%; - margin: 10px auto; - margin-bottom: 50px; + width: 75%; + margin: 0px 10px 25px 20px; } .search-box-container { @@ -15,4 +13,9 @@ display: flex; flex-direction: column; justify-content: space-evenly +} + +.search-header { + display: flex; + align-items: center; } \ No newline at end of file diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index 63bb9b29..295cb316 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -1,52 +1,18 @@ -import PersonIcon from '@mui/icons-material/Person'; -import { Box, Button, ButtonGroup, Modal } from '@mui/material'; +import { Button, ButtonGroup } from '@mui/material'; import _ from 'lodash'; import React, { Component } from 'react'; import buildNewRxRequest from '../../util/buildScript.2017071.js'; -import { defaultValues, shortNameMap, types } from '../../util/data'; +import { shortNameMap, types } from '../../util/data'; import { getAge } from '../../util/fhir'; import { retrieveLaunchContext } from '../../util/util'; import './request.css'; import InProgressFormBox from './InProgressFormBox/InProgressFormBox.js'; -import Accordion from '@mui/material/Accordion'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; - -import PatientSearchBar from './PatientSearchBar/PatientSearchBar.js'; - -// const style = { -// position: 'absolute', -// top: '50%', -// left: '50%', -// flexDirection: 'column', -// width: '80%', -// height: '70%', -// overflowY: 'scroll', -// transform: 'translate(-50%, -50%)', -// display: 'flex', -// bgcolor: 'background.paper', -// border: '2px solid #000', -// borderBottom: '2px solid black', -// boxShadow: 24, -// p: 4, -// padding: '50px' -// }; + export default class RequestBox extends Component { constructor(props) { super(props); this.state = { - expanded: false, - patientList: [], - patient: {}, - prefetchedResources: new Map(), - codeValues: defaultValues, - code: null, - codeSystem: null, - display: null, - request: {}, gatherCount: 0, - response: {} }; this.renderRequestResources = this.renderRequestResources.bind(this); @@ -60,8 +26,7 @@ export default class RequestBox extends Component { // TODO - see how to submit response for alternative therapy replaceRequestAndSubmit(request) { - this.setState({ request: request }); - // Submit the cds hook request. + this.props.callback(request,request); // Submit the cds hook request. this.submitOrderSign(request); } @@ -69,14 +34,14 @@ export default class RequestBox extends Component { prepPrefetch() { const preppedResources = new Map(); - Object.keys(this.state.prefetchedResources).forEach(resourceKey => { + Object.keys(this.props.prefetchedResources).forEach(resourceKey => { let resourceList = []; - if (Array.isArray(this.state.prefetchedResources[resourceKey])) { - resourceList = this.state.prefetchedResources[resourceKey].map(resource => { + if (Array.isArray(this.props.prefetchedResources[resourceKey])) { + resourceList = this.props.prefetchedResources[resourceKey].map(resource => { return resource; }); } else { - resourceList = this.state.prefetchedResources[resourceKey]; + resourceList = this.props.prefetchedResources[resourceKey]; } preppedResources.set(resourceKey, resourceList); @@ -85,27 +50,27 @@ export default class RequestBox extends Component { } submitPatientView = () => { - this.props.submitInfo(this.prepPrefetch(), null, this.state.patient, 'patient-view'); + this.props.submitInfo(this.prepPrefetch(), null, this.props.patient, 'patient-view'); }; submitOrderSelect = () => { - if (!_.isEmpty(this.state.request)) { + if (!_.isEmpty(this.props.request)) { this.props.submitInfo( this.prepPrefetch(), - this.state.request, - this.state.patient, + this.props.request, + this.props.patient, 'order-select' ); } }; submitOrderSign = request => { - this.props.submitInfo(this.prepPrefetch(), request, this.state.patient, 'order-sign'); + this.props.submitInfo(this.prepPrefetch(), request, this.props.patient, 'order-sign'); }; submit = () => { - if (!_.isEmpty(this.state.request)) { - this.submitOrderSign(this.state.request); + if (!_.isEmpty(this.props.request)) { + this.submitOrderSign(this.props.request); } }; @@ -116,12 +81,12 @@ export default class RequestBox extends Component { this.state.prefetchCompleted ) { // if the prefetch contains a medicationRequests bundle - if (this.state.prefetchedResources.medicationRequests) { + if (this.props.prefetchedResources.medicationRequests) { this.submitPatientView(); } // we could use this in the future to send order-select //// if the prefetch contains a request - //if (this.state.prefetchedResources.request) { + //if (this.props.prefetchedResources.request) { // this.submitOrderSelect(); //} } @@ -131,50 +96,10 @@ export default class RequestBox extends Component { this.setState({ [elementName]: text }); }; - updateStateList = (elementName, text) => { - this.setState(prevState => { - return { [elementName]: [...prevState[elementName], text] }; - }); - }; - - updateStateMap = (elementName, key, text) => { - this.setState(prevState => { - if (!prevState[elementName][key]) { - prevState[elementName][key] = []; - } - return { [elementName]: { ...prevState[elementName], [key]: text } }; - }); - }; - - clearState = () => { - this.setState({ - prefetchedResources: new Map(), - practitioner: {}, - coverage: {}, - response: {} - }); - }; - - getPatients = () => { - this.props.client - .request(this.props.patientFhirQuery, { flat: true }) - .then(result => { - this.setState({ - patientList: result, - expanded: true - }); - }) - .catch(e => { - this.setState({ - patientList: e - }); - }); - }; - emptyField = (empty); renderPatientInfo() { - const patient = this.state.patient; + const patient = this.props.patient; if (Object.keys(patient).length === 0) { return
; } @@ -211,20 +136,20 @@ export default class RequestBox extends Component { Coding
- Code: {this.state.code ? this.state.code : this.emptyField} + Code: {this.props.code ? this.props.code : this.emptyField}
- System: {this.state.codeSystem ? shortNameMap[this.state.codeSystem] : this.emptyField} + System: {this.props.codeSystem ? shortNameMap[this.props.codeSystem] : this.emptyField}
- Display: {this.state.display ? this.state.display : this.emptyField} + Display: {this.props.display ? this.props.display : this.emptyField}
); } renderPrefetchedResources() { - const prefetchMap = new Map(Object.entries(this.state.prefetchedResources)); + const prefetchMap = new Map(Object.entries(this.props.prefetchedResources)); if (prefetchMap.size > 0) { return this.renderRequestResources(prefetchMap); } @@ -300,7 +225,7 @@ export default class RequestBox extends Component { launchSmartOnFhirApp = () => { console.log('Launch SMART on FHIR App'); - let userId = this.state.prefetchedResources?.practitioner?.id; + let userId = this.props.prefetchedResources?.practitioner?.id; if (!userId) { console.log( 'Practitioner not populated from prefetch, using default from config: ' + @@ -310,12 +235,12 @@ export default class RequestBox extends Component { } let link = { - appContext: 'user=' + userId + '&patient=' + this.state.patient.id, + appContext: 'user=' + userId + '&patient=' + this.props.patient.id, type: 'smart', url: this.props.smartAppUrl }; - retrieveLaunchContext(link, this.state.patient.id, this.props.client.state).then(result => { + retrieveLaunchContext(link, this.props.patient.id, this.props.client.state).then(result => { link = result; console.log(link); // launch the application in a new window @@ -341,10 +266,10 @@ export default class RequestBox extends Component { response = undefined; if (!this.isOrderNotSelected()) { - if (Object.keys(this.state.request).length > 0) { - order = `${this.state.request.resourceType}/${this.state.request.id}`; - if (this.state.request.insurance && this.state.request.insurance.length > 0) { - coverage = `${this.state.request.insurance[0].reference}`; + if (Object.keys(this.props.request).length > 0) { + order = `${this.props.request.resourceType}/${this.props.request.id}`; + if (this.props.request.insurance && this.props.request.insurance.length > 0) { + coverage = `${this.props.request.insurance[0].reference}`; } } } @@ -357,8 +282,8 @@ export default class RequestBox extends Component { } } - if (Object.keys(this.state.response).length > 0) { - response = `QuestionnaireResponse/${this.state.response.id}`; + if (Object.keys(this.props.response).length > 0) { + response = `QuestionnaireResponse/${this.props.response.id}`; } if (order && response) { @@ -375,7 +300,7 @@ export default class RequestBox extends Component { let linkCopy = Object.assign({}, link); - return retrieveLaunchContext(linkCopy, this.state.patient.id, this.props.client.state).then( + return retrieveLaunchContext(linkCopy, this.props.patient.id, this.props.client.state).then( result => { linkCopy = result; return linkCopy; @@ -391,9 +316,9 @@ export default class RequestBox extends Component { // build the NewRx Message var newRx = buildNewRxRequest( - this.state.prefetchedResources.patient, - this.state.prefetchedResources.practitioner, - this.state.request + this.props.prefetchedResources.patient, + this.props.prefetchedResources.practitioner, + this.props.request ); console.log(newRx); const serializer = new XMLSerializer(); @@ -423,101 +348,51 @@ export default class RequestBox extends Component { }; isOrderNotSelected() { - return Object.keys(this.state.request).length === 0; + return Object.keys(this.props.request).length === 0; } isPatientNotSelected() { - return Object.keys(this.state.patient).length === 0; + return Object.keys(this.props.patient).length === 0; } - handleChange = () => (event, isExpanded) => { - if (isExpanded) { - this.getPatients(); - } else { - this.setState({ - patientList: [], - expanded: false - }); - } - this.setState({ expanded: isExpanded ? true: false}); - }; - render() { const disableSendToCRD = this.isOrderNotSelected() || this.props.loading; const disableSendRx = this.isOrderNotSelected() || this.props.loading; const disableLaunchSmartOnFhir = this.isPatientNotSelected(); return (
-
- - } - aria-controls="panel1a-content" - id="panel1a-header" - > - - - -
- - {this.state.patientList instanceof Error - ? this.renderError() - : } - -
-
-
-
-
- {this.state.patient.id ? ( - Patient ID: {this.state.patient.id} - ) : ( - No patient selected - )} -
-
- {this.renderPatientInfo()} - {this.renderPrefetchedResources()} -
-
- {this.state.patient.id ? ( -
- - - - - - + { this.props.patient.id ? ( +
+
+
+ Patient ID: {this.props.patient.id} +
+
+ {this.renderPatientInfo()} + {this.renderPrefetchedResources()} +
+
+
+ + + + + + +
) : ( - + )} -
-
); } diff --git a/src/components/RequestBox/request.css b/src/components/RequestBox/request.css index c0a3d539..14b6aedd 100644 --- a/src/components/RequestBox/request.css +++ b/src/components/RequestBox/request.css @@ -15,7 +15,7 @@ height: auto; padding: 10px; border-radius: 5px; - background-color: rgb(248, 248, 248) + background-color: rgb(248, 248, 248); } .select-button{ diff --git a/src/components/SMARTBox/PatientBox.js b/src/components/SMARTBox/PatientBox.js index b95d49c1..ea337028 100644 --- a/src/components/SMARTBox/PatientBox.js +++ b/src/components/SMARTBox/PatientBox.js @@ -134,6 +134,10 @@ export default class PatientBox extends Component { } } else { this.updatePrefetchRequest(null, patient, this.props.defaultUser); + this.props.callback('request', {}); + this.props.callback('code', null); + this.props.callback('codeSystem', null); + this.props.callback('display', null); } if (this.state.response) { @@ -388,7 +392,6 @@ export default class PatientBox extends Component { render() { const patient = this.props.patient; - console.log('patient --- > ', patient); let name = ''; if (patient.name) { name = {`${patient.name[0].given[0]} ${patient.name[0].family}`} ; diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index adecf489..99fd6d66 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -1,16 +1,23 @@ import React, { Component } from 'react'; - +import { Button, Box, IconButton } from '@mui/material'; +import PersonIcon from '@mui/icons-material/Person'; +import RefreshIcon from '@mui/icons-material/Refresh'; import DisplayBox from '../components/DisplayBox/DisplayBox'; -import ConsoleBox from '../components/ConsoleBox/ConsoleBox'; import '../index.css'; import '../components/ConsoleBox/consoleBox.css'; import SettingsBox from '../components/SettingsBox/SettingsBox'; import RequestBox from '../components/RequestBox/RequestBox'; import buildRequest from '../util/buildRequest.js'; -import { types } from '../util/data.js'; +import { types, defaultValues } from '../util/data.js'; import { createJwt, setupKeys } from '../util/auth'; import env from 'env-var'; import FHIR from 'fhirclient'; +import Accordion from '@mui/material/Accordion'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; + +import PatientSearchBar from '../components/RequestBox/PatientSearchBar/PatientSearchBar'; export default class RequestBuilder extends Component { constructor(props) { @@ -19,11 +26,19 @@ export default class RequestBuilder extends Component { keypair: null, loading: false, logs: [], - patient: {}, + patient: {}, + expanded: false, + patientList: [], response: null, + code: null, + codeSystem: null, + display: null, + prefetchedResources: new Map(), + request: {}, showSettings: false, token: null, client: this.props.client, + codeValues: defaultValues, // Configurable values alternativeTherapy: env.get('REACT_APP_ALT_DRUG').asBool(), baseUrl: env.get('REACT_APP_EHR_BASE').asString(), @@ -63,6 +78,8 @@ export default class RequestBuilder extends Component { if (!this.state.client) { this.reconnectEhr(); } else { + // Call patients on load of page + this.getPatients(); this.setState({ baseUrl: this.state.client.state.serverUrl }); this.setState({ ehrUrl: this.state.client.state.serverUrl }); } @@ -90,8 +107,15 @@ export default class RequestBuilder extends Component { updateStateElement = (elementName, text) => { this.setState({ [elementName]: text }); + // if the patientFhirQuery is updated, make a call to get the patients + if (elementName === 'patientFhirQuery') { + setTimeout(() => { + this.getPatients(); + }, 1000); + } }; + timeout = time => { let controller = new AbortController(); setTimeout(() => controller.abort(), time * 1000); @@ -182,6 +206,48 @@ export default class RequestBuilder extends Component { this.requestBox.current.replaceRequestAndSubmit(resource); } + getPatients = () => { + this.props.client + .request(this.state.patientFhirQuery, { flat: true }) + .then(result => { + this.setState({ + patientList: result, + }); + }) + .catch(e => { + this.setState({ + patientList: e + }); + }); + }; + + updateStateList = (elementName, text) => { + this.setState(prevState => { + return { [elementName]: [...prevState[elementName], text] }; + }); + }; + + updateStateMap = (elementName, key, text) => { + this.setState(prevState => { + if (!prevState[elementName][key]) { + prevState[elementName][key] = []; + } + return { [elementName]: { ...prevState[elementName], [key]: text } }; + }); + }; + + clearState = () => { + this.setState({ + prefetchedResources: new Map(), + practitioner: {}, + coverage: {}, + response: {} + }); + }; + handleChange = () => (event, isExpanded) => { + this.setState({ expanded: isExpanded ? true: false}); + }; + render() { return (
@@ -203,6 +269,53 @@ export default class RequestBuilder extends Component { Reconnect EHR
+
+ + } + aria-controls="panel1a-content" + id="panel1a-header" + style={{marginLeft: '45%'}} + > + + + + {this.state.patientList.length > 0 && this.state.expanded ? +
+ + {this.state.patientList instanceof Error + ? this.renderError() + : } + +
+ : + } + +
+
+ this.getPatients()} + size="large" + > + + +
{this.state.showSettings && ( @@ -222,6 +335,13 @@ export default class RequestBuilder extends Component { fhirServerUrl={this.state.baseUrl} fhirVersion={'r4'} patientId={this.state.patient.id} + patient={this.state.patient} + request={this.state.request} + response={this.state.response} + code={this.state.code} + codeSystem={this.state.codeSystem} + display={this.state.display} + prefetchedResources={this.state.prefetchedResources} launchUrl={this.state.launchUrl} responseExpirationDays={this.state.responseExpirationDays} pimsUrl={this.state.pimsUrl} @@ -233,12 +353,6 @@ export default class RequestBuilder extends Component { patientFhirQuery ={this.state.patientFhirQuery} />
-
- -
-
-
-
diff --git a/src/index.css b/src/index.css index 5ab4fed4..b720cfa8 100644 --- a/src/index.css +++ b/src/index.css @@ -15,8 +15,6 @@ body { box-shadow: none; } - - .floating-label { position: absolute; pointer-events: none; @@ -307,11 +305,12 @@ input:not(:focus):not([value=""]):valid ~ .floating-label{ } .nav-header{ - margin-bottom: 10px; - height: 55px; - padding:10px; - border-bottom: 1px solid black; - background-color: #005B94; + margin-bottom: 10px; + display: flow; + height: 55px; + padding:10px; + border-bottom: 1px solid black; + background-color: #005B94; } .loading{ From f5084681d0819f8a4e3bf267a93dd91a93918e0b Mon Sep 17 00:00:00 2001 From: Ariel Virgulto Date: Tue, 26 Dec 2023 20:52:59 -0600 Subject: [PATCH 17/36] Delete console box, update spacing of select btn --- src/components/ConsoleBox/ConsoleBox.js | 69 ---------------- src/components/ConsoleBox/consoleBox.css | 78 ------------------- .../InProgressFormBox/InProgressFormBox.js | 2 +- src/components/SMARTBox/smart.css | 1 + src/containers/RequestBuilder.js | 1 - 5 files changed, 2 insertions(+), 149 deletions(-) delete mode 100644 src/components/ConsoleBox/ConsoleBox.js delete mode 100644 src/components/ConsoleBox/consoleBox.css diff --git a/src/components/ConsoleBox/ConsoleBox.js b/src/components/ConsoleBox/ConsoleBox.js deleted file mode 100644 index 056f7226..00000000 --- a/src/components/ConsoleBox/ConsoleBox.js +++ /dev/null @@ -1,69 +0,0 @@ -import { Button } from '@mui/material'; -import React, { Component } from 'react'; - -export default class ConsoleBox extends Component { - constructor(props) { - super(props); - this.state = { - showStatus: 'hideConsole', - headerStatus: 'collapseHeader' - }; - - this.toggleConsole = this.toggleConsole.bind(this); - } - - handleAddition = (e, { value }) => { - this.setState({ - options: [{ text: value, value }, ...this.state.options] - }); - }; - - handleChange = (e, { value }) => { - this.props.updateCB(this.props.elementName, value); - this.setState({ currentValue: value }); - }; - - toggleConsole() { - if (this.state.showStatus === 'showConsole') { - this.setState({ showStatus: 'hideConsole' }); - this.setState({ headerStatus: 'collapseHeader' }); - } else { - this.setState({ showStatus: 'showConsole' }); - this.setState({ headerStatus: 'showHeader' }); - } - } - - render() { - try { - var objDiv = document.getElementById('your_div'); - if (objDiv) { - objDiv.scrollTop = objDiv.scrollHeight; - } - } catch (error) { - console.log('Encountered error', error); - } - let i = 0; - return ( -
- -
- {this.props.logs.map(element => { - i++; - return ( -
- {' '} - {element.content} -
- ); - })} -
-
- ); - } -} diff --git a/src/components/ConsoleBox/consoleBox.css b/src/components/ConsoleBox/consoleBox.css deleted file mode 100644 index 69359024..00000000 --- a/src/components/ConsoleBox/consoleBox.css +++ /dev/null @@ -1,78 +0,0 @@ -.consoleMain{ - border-width:1px 5px 3px 5px; - border-style: solid solid solid solid; - border-color: black; - background-color: #333333; - color:white; - overflow-y:scroll; - overflow-x: wrap; - word-break:break-all; - font-family: 'Courier New', Courier, monospace; - transition: all .2s ease; - /* transition-duration: .2s; */ - transition-delay: .25s; -} -.resize{ - resize: vertical; - transition-duration:0s; - transition-delay:0s; -} -.showConsole{ - height:200px; - - -} -.hideConsole{ - resize:none; - height:0px !important; - border-width: 0px; - transition-property: all; - transition-duration: .2s; - transition-delay: 0s; -} - -.consoleHeader{ - border: 3px solid black; - background-color: black; - color: white; - margin-top: 30px; - font-family: 'Courier New', Courier, monospace; - display:block; - font-size: 18px; - text-align: center; - transition-property: all; - transition-duration: .2s; - transition-delay: 0s; -} - -.showHeader{ - width:100%; -} - -.collapseHeader{ - width: 250px; - color: black; - text-align:center; - border-width: 1px 1px 1px 1px; - background-color: white; - transition-property: all; - transition-duration: .2s; - transition-delay: .25s; -} - -.showHeader:hover ~ .resize{ - transition-delay:.25s; - transition-duration:.4s; -} - -.errorClass{ - color:#dc3545; -} - -.warningClass{ - color: #ffc107; -} - -.infoClass{ - color: #17a2b8; -} \ No newline at end of file diff --git a/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js b/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js index 7cfe2573..293003e8 100644 --- a/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js +++ b/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js @@ -1,4 +1,4 @@ -import { Box, Button, Paper, Typography, ButtonGroup } from '@mui/material'; +import { Box, Button, Typography, ButtonGroup } from '@mui/material'; import React from 'react'; import './InProgressFormBoxStyle.css'; diff --git a/src/components/SMARTBox/smart.css b/src/components/SMARTBox/smart.css index 69043330..243de77e 100644 --- a/src/components/SMARTBox/smart.css +++ b/src/components/SMARTBox/smart.css @@ -86,6 +86,7 @@ html{ .select-btn { height: 40px; align-self: center; + margin-top: 25px !important; } .emptyForm { diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index 99fd6d66..f6bf000d 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -4,7 +4,6 @@ import PersonIcon from '@mui/icons-material/Person'; import RefreshIcon from '@mui/icons-material/Refresh'; import DisplayBox from '../components/DisplayBox/DisplayBox'; import '../index.css'; -import '../components/ConsoleBox/consoleBox.css'; import SettingsBox from '../components/SettingsBox/SettingsBox'; import RequestBox from '../components/RequestBox/RequestBox'; import buildRequest from '../util/buildRequest.js'; From c080375618eab75d7996f795c3699c881c3b3471 Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Thu, 14 Dec 2023 14:04:05 -0500 Subject: [PATCH 18/36] Prettier and fix typo in README --- README.md | 92 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 830d0c66..f4a077d8 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,67 @@ # Request Generator -This subproject provides a small web application that is capable of generating requests and displaying the CDS Hooks cards that are provided as a response. This project is written in JavaScript and runs in [node.js](https://nodejs.org/en/). + +This subproject provides a small web application that is capable of generating requests and displaying the CDS Hooks cards that are provided as a response. This project is written in JavaScript and runs in [node.js](https://nodejs.org/en/). ## Running the request generator standalone + 1. Install node.js 2. Clone the repository - * `git clone https://github.com/mcode/request-generator.git` + +- `git clone https://github.com/mcode/request-generator.git` + 3. Install the dependencies - * `cd request-generator` - * `npm install` + +- `cd request-generator` +- `npm install` + 4. Run the application - * `npm start` + +- `npm start` This should open a browser window directed to the value set in `REACT_APP_URL`. The request-generator assumes the CRD server is running on the default value set for `REACT_APP_SERVER`. This can be changed in the properties file [.env](./.env). [The following section](./README.md#how-to-override-defaults) lists the default values for these environment variables. ## Versions + This application requires node v14. -## Keys -Embedded in the application are the public and provate keys used to generate and verify JSON Web Tokens (JWT) that are used to authenticate/authorize calls to a CDS-Hooks service. The public key is contained in the public/.well-known/jwks.json document. The private key is contained in src/keys/crdPrivateKey.js file. The keys were generated from https://mkjwk.org/. To update these keys you can generate a new key pair from this site, ensure that you request the Show X.509 option is set to yes. Once generated you can replace the public and private keys. You will also need to update the src/utils/auth.js file with the corrisponding key information. +## Keys + +Embedded in the application are the public and private keys used to generate and verify JSON Web Tokens (JWT) that are used to authenticate/authorize calls to a CDS-Hooks service. The public key is contained in the public/.well-known/jwks.json document. The private key is contained in src/keys/crdPrivateKey.js file. The keys were generated from https://mkjwk.org/. To update these keys you can generate a new key pair from this site, ensure that you request the Show X.509 option is set to yes. Once generated you can replace the public and private keys. You will also need to update the src/utils/auth.js file with the corresponding key information. ### How To Override Defaults + The .env file contains the default URI paths, these can be overwritten from the start command as follows: - `REACT_APP_LAUNCH_URL=http://example.com PORT=6000 npm start` - -Following are a list of modifiable paths: - -| URI Name | Default | -| -----------------------|---------------------------------------------------------------------------| -| REACT_APP_AUTH | `http://localhost:8180` | -| REACT_APP_EHR_SERVER | `http://localhost:8080/test-ehr/r4` | -| REACT_APP_CDS_SERVICE | `http://localhost:8090/cds-services` | -| REACT_APP_PUBLIC_KEYS | `http://localhost:3001/public_keys` | -| REACT_APP_LAUNCH_URL | `http://localhost:4040/launch` | -| REACT_APP_PIMS_SERVER | `http://localhost:5051/doctorOrders/api/addRx` | -| REACT_APP_REALM | `ClientFhirServer` | -| REACT_APP_CLIENT | `app-login` | -| REACT_APP_SERVER | `http://localhost:8090` | -| REACT_APP_EHR_BASE | `http://localhost:8080/test-ehr/r4` | -| REACT_APP_EHR_SERVER_TO_BE_SENT_TO_REMS_ADMIN_FOR_PREFETCH | `http://localhost:8080/test-ehr/r4` | -| REACT_APP_ORDER_SIGN | `rems-order-sign` | -| REACT_APP_ORDER_SELECT | `rems-order-select` | -| REACT_APP_PATIENT_VIEW | `rems-patient-view` | -| REACT_APP_USER | `alice` | -| REACT_APP_PASSWORD | `alice` | -| REACT_APP_ALT_DRUG | `true` | -| REACT_APP_SMART_LAUNCH_URL | `http://localhost:4040/` | -| REACT_APP_DEFAULT_USER | `pra1234` | -| REACT_APP_RESPONSE_EXPIRATION_DAYS | `30` | -| REACT_APP_HOMEPAGE | `http://localhost:8080` | -| REACT_APP_URL | `http://localhost:3000` | -| REACT_APP_URL_FILTER | `http://localhost:3000/*` | -| REACT_APP_EHR_LINK | `http://localhost:8080/ehr-server/` | -| HTTPS | `false` | -| HTTPS_KEY_PATH | `server.key` | -| HTTPS_CERT_PATH | `server.cert` | -| REACT_APP_PATIENT_FHIR_QUERY | `Patient?_sort=identifier&_count=12` | +`REACT_APP_LAUNCH_URL=http://example.com PORT=6000 npm start` + +Following are a list of modifiable paths: + +| URI Name | Default | +| ---------------------------------------------------------- | --------------------------------------------------------------- | +| REACT_APP_AUTH | `http://localhost:8180` | +| REACT_APP_EHR_SERVER | `http://localhost:8080/test-ehr/r4` | +| REACT_APP_CDS_SERVICE | `http://localhost:8090/cds-services` | +| REACT_APP_PUBLIC_KEYS | `http://localhost:3000/request-generator/.well-known/jwks.json` | +| REACT_APP_LAUNCH_URL | `http://localhost:4040/launch` | +| REACT_APP_PIMS_SERVER | `http://localhost:5051/doctorOrders/api/addRx` | +| REACT_APP_REALM | `ClientFhirServer` | +| REACT_APP_CLIENT | `app-login` | +| REACT_APP_SERVER | `http://localhost:8090` | +| REACT_APP_EHR_BASE | `http://localhost:8080/test-ehr/r4` | +| REACT_APP_EHR_SERVER_TO_BE_SENT_TO_REMS_ADMIN_FOR_PREFETCH | `http://localhost:8080/test-ehr/r4` | +| REACT_APP_ORDER_SIGN | `rems-order-sign` | +| REACT_APP_ORDER_SELECT | `rems-order-select` | +| REACT_APP_PATIENT_VIEW | `rems-patient-view` | +| REACT_APP_USER | `alice` | +| REACT_APP_PASSWORD | `alice` | +| REACT_APP_ALT_DRUG | `true` | +| REACT_APP_SMART_LAUNCH_URL | `http://localhost:4040/` | +| REACT_APP_DEFAULT_USER | `pra1234` | +| REACT_APP_RESPONSE_EXPIRATION_DAYS | `30` | +| REACT_APP_HOMEPAGE | `http://localhost:8080` | +| REACT_APP_URL | `http://localhost:3000` | +| REACT_APP_URL_FILTER | `http://localhost:3000/*` | +| REACT_APP_EHR_LINK | `http://localhost:8080/ehr-server/` | +| HTTPS | `false` | +| HTTPS_KEY_PATH | `server.key` | +| HTTPS_CERT_PATH | `server.cert` | +| REACT_APP_PATIENT_FHIR_QUERY | `Patient?_sort=identifier&_count=12` | From a980cd9684aa46864e44f813d29e1cfe4c440356 Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Tue, 2 Jan 2024 17:13:50 -0500 Subject: [PATCH 19/36] Alphabetize environment variables --- .env | 45 ++++++++++++++++++++------------------------- README.md | 38 +++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/.env b/.env index 661d24f2..380805a7 100644 --- a/.env +++ b/.env @@ -1,36 +1,31 @@ -# Development URLS - -REACT_APP_REALM = ClientFhirServer +HTTPS = false +HTTPS_CERT_PATH = server.cert +HTTPS_KEY_PATH = server.key +REACT_APP_ALT_DRUG = true +REACT_APP_AUTH = http://localhost:8180 +REACT_APP_CDS_SERVICE = http://localhost:8090/cds-services REACT_APP_CLIENT = app-login REACT_APP_CLIENT_SCOPES = launch offline_access openid profile user/Patient.read patient/Patient.read user/Practitioner.read -REACT_APP_AUTH = http://localhost:8180 -REACT_APP_SERVER = http://localhost:8090 +REACT_APP_DEFAULT_USER = pra1234 +REACT_APP_EHR_BASE = http://localhost:8080/test-ehr/r4 +REACT_APP_EHR_LINK = http://localhost:8080/ehr-server/ REACT_APP_EHR_SERVER = http://localhost:8080/test-ehr/r4 REACT_APP_EHR_SERVER_TO_BE_SENT_TO_REMS_ADMIN_FOR_PREFETCH = http://localhost:8080/test-ehr/r4 -REACT_APP_EHR_BASE = http://localhost:8080/test-ehr/r4 -REACT_APP_CDS_SERVICE = http://localhost:8090/cds-services REACT_APP_GENERATE_JWT = true -REACT_APP_ORDER_SIGN = rems-order-sign +REACT_APP_GH_PAGES=false +REACT_APP_HOMEPAGE = http://localhost:8080 +REACT_APP_LAUNCH_URL = http://localhost:4040/launch REACT_APP_ORDER_SELECT = rems-order-select -REACT_APP_PATIENT_VIEW = rems-patient-view -REACT_APP_PATIENT_FHIR_QUERY = Patient?_sort=identifier&_count=12 -REACT_APP_USER = alice +REACT_APP_ORDER_SIGN = rems-order-sign REACT_APP_PASSWORD = alice +REACT_APP_PATIENT_FHIR_QUERY = Patient?_sort=identifier&_count=12 +REACT_APP_PATIENT_VIEW = rems-patient-view +REACT_APP_PIMS_SERVER = http://localhost:5051/doctorOrders/api/addRx REACT_APP_PUBLIC_KEYS = http://localhost:3000/request-generator/.well-known/jwks.json -REACT_APP_ALT_DRUG = true -REACT_APP_LAUNCH_URL = http://localhost:4040/launch -REACT_APP_SMART_LAUNCH_URL = http://localhost:4040/ -REACT_APP_DEFAULT_USER = pra1234 +REACT_APP_REALM = ClientFhirServer REACT_APP_RESPONSE_EXPIRATION_DAYS = 30 -REACT_APP_PIMS_SERVER = http://localhost:5051/doctorOrders/api/addRx -REACT_APP_HOMEPAGE = http://localhost:8080 +REACT_APP_SERVER = http://localhost:8090 +REACT_APP_SMART_LAUNCH_URL = http://localhost:4040/ REACT_APP_URL = http://localhost:3000 REACT_APP_URL_FILTER = http://localhost:3000/* -REACT_APP_EHR_LINK = http://localhost:8080/ehr-server/ -HTTPS = false -HTTPS_KEY_PATH = server.key -HTTPS_CERT_PATH = server.cert -REACT_APP_GH_PAGES=false -# To Override start command: -# REACT_APP_AUTH=http://example.com PORT=6000 npm start -# Note that .env values can only be accessed by react app starting with 'REACT_APP_' +REACT_APP_USER = alice \ No newline at end of file diff --git a/README.md b/README.md index f4a077d8..dd3dc70a 100644 --- a/README.md +++ b/README.md @@ -31,37 +31,37 @@ Embedded in the application are the public and private keys used to generate and ### How To Override Defaults The .env file contains the default URI paths, these can be overwritten from the start command as follows: -`REACT_APP_LAUNCH_URL=http://example.com PORT=6000 npm start` +`REACT_APP_LAUNCH_URL=http://example.com PORT=6000 npm start` or by specifying the environment variables and desired values in a `.env.local`. Following are a list of modifiable paths: | URI Name | Default | | ---------------------------------------------------------- | --------------------------------------------------------------- | +| HTTPS | `false` | +| HTTPS_CERT_PATH | `server.cert` | +| HTTPS_KEY_PATH | `server.key` | +| REACT_APP_ALT_DRUG | `true` | | REACT_APP_AUTH | `http://localhost:8180` | -| REACT_APP_EHR_SERVER | `http://localhost:8080/test-ehr/r4` | | REACT_APP_CDS_SERVICE | `http://localhost:8090/cds-services` | -| REACT_APP_PUBLIC_KEYS | `http://localhost:3000/request-generator/.well-known/jwks.json` | -| REACT_APP_LAUNCH_URL | `http://localhost:4040/launch` | -| REACT_APP_PIMS_SERVER | `http://localhost:5051/doctorOrders/api/addRx` | -| REACT_APP_REALM | `ClientFhirServer` | | REACT_APP_CLIENT | `app-login` | -| REACT_APP_SERVER | `http://localhost:8090` | +| REACT_APP_DEFAULT_USER | `pra1234` | | REACT_APP_EHR_BASE | `http://localhost:8080/test-ehr/r4` | +| REACT_APP_EHR_LINK | `http://localhost:8080/ehr-server/` | +| REACT_APP_EHR_SERVER | `http://localhost:8080/test-ehr/r4` | | REACT_APP_EHR_SERVER_TO_BE_SENT_TO_REMS_ADMIN_FOR_PREFETCH | `http://localhost:8080/test-ehr/r4` | -| REACT_APP_ORDER_SIGN | `rems-order-sign` | +| REACT_APP_HOMEPAGE | `http://localhost:8080` | +| REACT_APP_LAUNCH_URL | `http://localhost:4040/launch` | | REACT_APP_ORDER_SELECT | `rems-order-select` | -| REACT_APP_PATIENT_VIEW | `rems-patient-view` | -| REACT_APP_USER | `alice` | +| REACT_APP_ORDER_SIGN | `rems-order-sign` | | REACT_APP_PASSWORD | `alice` | -| REACT_APP_ALT_DRUG | `true` | -| REACT_APP_SMART_LAUNCH_URL | `http://localhost:4040/` | -| REACT_APP_DEFAULT_USER | `pra1234` | +| REACT_APP_PATIENT_FHIR_QUERY | `Patient?_sort=identifier&_count=12` | +| REACT_APP_PATIENT_VIEW | `rems-patient-view` | +| REACT_APP_PIMS_SERVER | `http://localhost:5051/doctorOrders/api/addRx` | +| REACT_APP_PUBLIC_KEYS | `http://localhost:3000/request-generator/.well-known/jwks.json` | +| REACT_APP_REALM | `ClientFhirServer` | | REACT_APP_RESPONSE_EXPIRATION_DAYS | `30` | -| REACT_APP_HOMEPAGE | `http://localhost:8080` | +| REACT_APP_SERVER | `http://localhost:8090` | +| REACT_APP_SMART_LAUNCH_URL | `http://localhost:4040/` | | REACT_APP_URL | `http://localhost:3000` | | REACT_APP_URL_FILTER | `http://localhost:3000/*` | -| REACT_APP_EHR_LINK | `http://localhost:8080/ehr-server/` | -| HTTPS | `false` | -| HTTPS_KEY_PATH | `server.key` | -| HTTPS_CERT_PATH | `server.cert` | -| REACT_APP_PATIENT_FHIR_QUERY | `Patient?_sort=identifier&_count=12` | +| REACT_APP_USER | `alice` | From f9137901c8c16946021005bbba18e17c1a111fce Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Thu, 14 Dec 2023 14:25:57 -0500 Subject: [PATCH 20/36] Document new environment variables --- README.md | 63 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index dd3dc70a..3ba75f6f 100644 --- a/README.md +++ b/README.md @@ -35,33 +35,36 @@ The .env file contains the default URI paths, these can be overwritten from the Following are a list of modifiable paths: -| URI Name | Default | -| ---------------------------------------------------------- | --------------------------------------------------------------- | -| HTTPS | `false` | -| HTTPS_CERT_PATH | `server.cert` | -| HTTPS_KEY_PATH | `server.key` | -| REACT_APP_ALT_DRUG | `true` | -| REACT_APP_AUTH | `http://localhost:8180` | -| REACT_APP_CDS_SERVICE | `http://localhost:8090/cds-services` | -| REACT_APP_CLIENT | `app-login` | -| REACT_APP_DEFAULT_USER | `pra1234` | -| REACT_APP_EHR_BASE | `http://localhost:8080/test-ehr/r4` | -| REACT_APP_EHR_LINK | `http://localhost:8080/ehr-server/` | -| REACT_APP_EHR_SERVER | `http://localhost:8080/test-ehr/r4` | -| REACT_APP_EHR_SERVER_TO_BE_SENT_TO_REMS_ADMIN_FOR_PREFETCH | `http://localhost:8080/test-ehr/r4` | -| REACT_APP_HOMEPAGE | `http://localhost:8080` | -| REACT_APP_LAUNCH_URL | `http://localhost:4040/launch` | -| REACT_APP_ORDER_SELECT | `rems-order-select` | -| REACT_APP_ORDER_SIGN | `rems-order-sign` | -| REACT_APP_PASSWORD | `alice` | -| REACT_APP_PATIENT_FHIR_QUERY | `Patient?_sort=identifier&_count=12` | -| REACT_APP_PATIENT_VIEW | `rems-patient-view` | -| REACT_APP_PIMS_SERVER | `http://localhost:5051/doctorOrders/api/addRx` | -| REACT_APP_PUBLIC_KEYS | `http://localhost:3000/request-generator/.well-known/jwks.json` | -| REACT_APP_REALM | `ClientFhirServer` | -| REACT_APP_RESPONSE_EXPIRATION_DAYS | `30` | -| REACT_APP_SERVER | `http://localhost:8090` | -| REACT_APP_SMART_LAUNCH_URL | `http://localhost:4040/` | -| REACT_APP_URL | `http://localhost:3000` | -| REACT_APP_URL_FILTER | `http://localhost:3000/*` | -| REACT_APP_USER | `alice` | +| URI Name | Default | +| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| HTTPS | `false` | +| HTTPS_CERT_PATH | `server.cert` | +| HTTPS_KEY_PATH | `server.key` | +| REACT_APP_ALT_DRUG | `true` | +| REACT_APP_AUTH | `http://localhost:8180` | +| REACT_APP_CDS_SERVICE | `http://localhost:8090/cds-services` | +| REACT_APP_CLIENT | `app-login` | +| REACT_APP_CLIENT_SCOPES | `launch offline_access openid profile user/Patient.read patient/Patient.read user/Practitioner.read` | +| REACT_APP_DEFAULT_USER | `pra1234` | +| REACT_APP_EHR_BASE | `http://localhost:8080/test-ehr/r4` | +| REACT_APP_EHR_LINK | `http://localhost:8080/ehr-server/` | +| REACT_APP_EHR_SERVER | `http://localhost:8080/test-ehr/r4` | +| REACT_APP_EHR_SERVER_TO_BE_SENT_TO_REMS_ADMIN_FOR_PREFETCH | `http://localhost:8080/test-ehr/r4` | +| REACT_APP_GENERATE_JWT | `true` | +| REACT_APP_GH_PAGES | `false` | +| REACT_APP_HOMEPAGE | `http://localhost:8080` | +| REACT_APP_LAUNCH_URL | `http://localhost:4040/launch` | +| REACT_APP_ORDER_SELECT | `rems-order-select` | +| REACT_APP_ORDER_SIGN | `rems-order-sign` | +| REACT_APP_PASSWORD | `alice` | +| REACT_APP_PATIENT_FHIR_QUERY | `Patient?_sort=identifier&_count=12` | +| REACT_APP_PATIENT_VIEW | `rems-patient-view` | +| REACT_APP_PIMS_SERVER | `http://localhost:5051/doctorOrders/api/addRx` | +| REACT_APP_PUBLIC_KEYS | `http://localhost:3000/request-generator/.well-known/jwks.json` | +| REACT_APP_REALM | `ClientFhirServer` | +| REACT_APP_RESPONSE_EXPIRATION_DAYS | `30` | +| REACT_APP_SERVER | `http://localhost:8090` | +| REACT_APP_SMART_LAUNCH_URL | `http://localhost:4040/` | +| REACT_APP_URL | `http://localhost:3000` | +| REACT_APP_URL_FILTER | `http://localhost:3000/*` | +| REACT_APP_USER | `alice` | From b5fae55479e8cd8e3fb2fc970ba7e82e07761af8 Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Wed, 20 Dec 2023 16:07:55 -0500 Subject: [PATCH 21/36] Add new section to README.md --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3ba75f6f..c7d8004c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ # Request Generator -This subproject provides a small web application that is capable of generating requests and displaying the CDS Hooks cards that are provided as a response. This project is written in JavaScript and runs in [node.js](https://nodejs.org/en/). +This project provides a small web application that is capable of generating requests and displaying the CDS Hooks cards that are provided as a response. This project is written in JavaScript and runs in [node.js](https://nodejs.org/en/). ## Running the request generator standalone -1. Install node.js +1. Install node.js v14 (using [`nvm`](https://github.com/nvm-sh/nvm) is optional, but easier) + +- `nvm install 14` +- `nvm use 14` + 2. Clone the repository - `git clone https://github.com/mcode/request-generator.git` @@ -18,11 +22,7 @@ This subproject provides a small web application that is capable of generating r - `npm start` -This should open a browser window directed to the value set in `REACT_APP_URL`. The request-generator assumes the CRD server is running on the default value set for `REACT_APP_SERVER`. This can be changed in the properties file [.env](./.env). [The following section](./README.md#how-to-override-defaults) lists the default values for these environment variables. - -## Versions - -This application requires node v14. +This should open a browser window directed to the value set in `REACT_APP_URL` followed by the string `/request-generator`. The request-generator assumes the REMS Admin is running on the default value set for `REACT_APP_SERVER`. This can be changed in the properties file [.env](./.env). [The following section](./README.md#how-to-override-defaults) lists the default values for these environment variables. ## Keys @@ -68,3 +68,38 @@ Following are a list of modifiable paths: | REACT_APP_URL | `http://localhost:3000` | | REACT_APP_URL_FILTER | `http://localhost:3000/*` | | REACT_APP_USER | `alice` | + +## How to launch as a SMART on FHIR app + +### Using a SMART App Launcher + +1. Go to a SMART app launcher, such as `http://moonshot-dev.mitre.org:4001/index.html` (MITRE) or `https://launch.smarthealthit.org/` (open to public). +2. For the App Launch URL, provide `http://localhost:3000/launch`. + +### Using Meld or a real EHR + +1. If you'd like to launch from Meld, [log in to Meld](https://meld.interop.community/) and follow steps 2-3. +2. The log in page will bring you to the My Sandboxes page. Go to your sandbox. +3. You will land on the Registered Apps page. Click on the circular plus button in the top-right corner and register the request-generator app manually with these settings: + + - Client Type: `Public Client` + - App Launch URI: `http://localhost:3000/launch` + - App Redirect URIs: `http://localhost:3000/#/index, http://localhost:4040/register,http://localhost:3000/index, http://localhost:4040/index` + - Scopes: `launch openid user/*.* offline_access profile` + +4. After registering request-generator, hover over it and click Launch. + + + +## How to launch a SMART on FHIR app from request-generator + +See the [following guide](./How-To-Launch-SMART-on-FHIR-Apps.md) for more information. From a155cc5b7743474f446adb310bbbeda985ddab27 Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Wed, 27 Dec 2023 13:39:43 -0500 Subject: [PATCH 22/36] Remove auto assign since we don't use it anymore --- .github/auto_assign.yml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .github/auto_assign.yml diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml deleted file mode 100644 index b63c47d6..00000000 --- a/.github/auto_assign.yml +++ /dev/null @@ -1,16 +0,0 @@ -# Set to true to add reviewers to pull requests -addReviewers: true - -# Set to true to add assignees to pull requests -addAssignees: false - -# A list of reviewers to be added to pull requests (GitHub user name) -reviewers: - - zacharyrobin - - KeeyanGhoreshi - - smalho01 - - plarocque4 - - kennyEung - -# A number of reviewers added to the pull request, Set to 0 to add all reviewers -numberOfReviewers: 0 From e1a72160f4276939c28923da6c9767887d0a439a Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Wed, 27 Dec 2023 13:43:38 -0500 Subject: [PATCH 23/36] Remove comment since its text is duplicated in the README --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 380805a7..76f60c85 100644 --- a/.env +++ b/.env @@ -28,4 +28,4 @@ REACT_APP_SERVER = http://localhost:8090 REACT_APP_SMART_LAUNCH_URL = http://localhost:4040/ REACT_APP_URL = http://localhost:3000 REACT_APP_URL_FILTER = http://localhost:3000/* -REACT_APP_USER = alice \ No newline at end of file +REACT_APP_USER = alice From 8529289337cb0658aba48d2dcfd1ba4817248127 Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Wed, 27 Dec 2023 14:48:26 -0500 Subject: [PATCH 24/36] Fix 'Each child in a list should have a unique key prop' warning --- src/components/Dashboard/Dashboard.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index 6c6acbef..c5304a3a 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -100,8 +100,8 @@ const Dashboard = props => { - {createIcons().map(option => ( -
+ {createIcons().map((option, index) => ( +
{option[1]} From 94d48fa21afef03e8c3910d7df90223a58fe443b Mon Sep 17 00:00:00 2001 From: Joyce Quach Date: Fri, 29 Dec 2023 17:24:58 -0500 Subject: [PATCH 25/36] Fix grammar --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c7d8004c..b7dd2b33 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ Embedded in the application are the public and private keys used to generate and ### How To Override Defaults -The .env file contains the default URI paths, these can be overwritten from the start command as follows: -`REACT_APP_LAUNCH_URL=http://example.com PORT=6000 npm start` or by specifying the environment variables and desired values in a `.env.local`. +The .env file contains the default URI paths, which can be overwritten from the start command as follows: +a) `REACT_APP_LAUNCH_URL=http://example.com PORT=6000 npm start` or b) by specifying the environment variables and desired values in a `.env.local`. Following are a list of modifiable paths: From 0c223e30f015faf1a315c86eaacf225eab97194c Mon Sep 17 00:00:00 2001 From: Keeyan Date: Wed, 3 Jan 2024 10:58:02 -0500 Subject: [PATCH 26/36] add standalone launch gateway (#87) * add standalone launch gateway * fix scopes --- src/components/App.js | 4 +- src/containers/Gateway/Gateway.jsx | 119 +++++++++++++++++++++++++++++ src/containers/Gateway/styles.jsx | 27 +++++++ src/containers/Index.jsx | 18 +++-- src/containers/Launch.jsx | 6 +- 5 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 src/containers/Gateway/Gateway.jsx create mode 100644 src/containers/Gateway/styles.jsx diff --git a/src/components/App.js b/src/components/App.js index 16aab97b..52bbf55d 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -6,7 +6,7 @@ import theme from '../containers/styles/theme'; import { ThemeProvider } from '@mui/styles'; import Launch from '../containers/Launch'; import Index from '../containers/Index'; - +import Gateway from '../containers/Gateway/Gateway'; const isGhPages = process.env.REACT_APP_GH_PAGES === 'true'; const Router = isGhPages ? HashRouter : BrowserRouter; const redirect = isGhPages ? '/#/index' : '/index'; @@ -24,7 +24,7 @@ const App = () => { } /> - } /> + } /> ); diff --git a/src/containers/Gateway/Gateway.jsx b/src/containers/Gateway/Gateway.jsx new file mode 100644 index 00000000..a360bb36 --- /dev/null +++ b/src/containers/Gateway/Gateway.jsx @@ -0,0 +1,119 @@ +import React, { memo, useState, useEffect } from 'react'; +import FHIR from 'fhirclient'; +import env from 'env-var'; +import { + Button, + FormControl, + FormHelperText, + IconButton, + TextField, + Accordion, + AccordionDetails +} from '@mui/material'; +import Stack from '@mui/material/Stack'; +import Autocomplete from '@mui/material/Autocomplete'; +import useStyles from './styles'; + +const Gateway = props => { + const classes = useStyles(); + const envFhir = env.get('REACT_APP_EHR_SERVER').asString(); + const envClient = env.get('REACT_APP_CLIENT').asString(); + const envScope = env.get('REACT_APP_CLIENT_SCOPES').asString().split(' '); + const [clientId, setClientId] = useState(envClient || ''); + const [fhirUrl, setFhirUrl] = useState(envFhir || ''); + const [scope, _setScope] = useState(envScope || []); + const setScope = value => { + // split by space to facilitate copy/pasting strings of scopes into the input + const sv = value.map(e => { + if (e) { + return e.split(' '); + } + }); + _setScope(sv.flat()); + }; + const submit = event => { + event.preventDefault(); + FHIR.oauth2.authorize({ + clientId: clientId, + scope: scope.join(' '), + redirectUri: props.redirect, + iss: fhirUrl + }); + }; + return ( +
+

Launch Request Generator

+
+ + + { + setFhirUrl(inputValue); + }} + options={[envFhir]} // TODO: can be updated later to include registered iss + renderInput={params => ( + + )} + /> + { + setClientId(inputValue); + }} + options={[envClient]} // TODO: can be updated later to match from iss from register page + renderInput={params => ( + + )} + /> + { + // scopes is the new full list, not the singular new value + setScope(scopes); + }} + defaultValue={['launch']} + renderInput={params => ( + + )} + /> + + + +
+
+ ); +}; + +export default memo(Gateway); diff --git a/src/containers/Gateway/styles.jsx b/src/containers/Gateway/styles.jsx new file mode 100644 index 00000000..ae2b96db --- /dev/null +++ b/src/containers/Gateway/styles.jsx @@ -0,0 +1,27 @@ +import { makeStyles } from '@mui/styles'; +export default makeStyles( + theme => ({ + '@global': { + body: { + backgroundColor: '#fafafa' + } + }, + gatewayDiv: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: '50px', + margin: '10% auto 0 auto', + width: '60%', + backgroundColor: '#fff' + }, + gatewayHeader: { + marginBottom: '25px' + }, + gatewayInput: { + padding: '50px' + } + }), + + { name: 'Gateway', index: 1 } +); diff --git a/src/containers/Index.jsx b/src/containers/Index.jsx index e1374b4f..3b74544b 100644 --- a/src/containers/Index.jsx +++ b/src/containers/Index.jsx @@ -12,13 +12,17 @@ const Index = props => { }); }, []); - return
- { client ? : -
-

Getting Client...

-
- } -
; + return ( +
+ {client ? ( + + ) : ( +
+

Getting Client...

+
+ )} +
+ ); }; export default Index; diff --git a/src/containers/Launch.jsx b/src/containers/Launch.jsx index fb9854c2..5db39527 100644 --- a/src/containers/Launch.jsx +++ b/src/containers/Launch.jsx @@ -23,7 +23,11 @@ const Launch = props => { }); }, []); - return

Launching...

; + return ( +
+

Launching...

+
+ ); }; export default memo(Launch); From e4e2235132b8afc66a539cfbe15e7335242864ab Mon Sep 17 00:00:00 2001 From: Ariel Virgulto Date: Thu, 4 Jan 2024 10:52:25 -0500 Subject: [PATCH 27/36] Move settings box above accordian --- src/containers/RequestBuilder.js | 20 ++++++++++++-------- src/index.css | 5 +++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index f6bf000d..0cbaa73d 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -267,6 +267,18 @@ export default class RequestBuilder extends Component { > Reconnect EHR +
+
+ {/*
*/} + {this.state.showSettings && ( +
+ +
+ )}
@@ -316,14 +328,6 @@ export default class RequestBuilder extends Component {
-
- {this.state.showSettings && ( - - )}
{/*for the ehr launch */} Date: Sun, 7 Jan 2024 20:46:58 -0800 Subject: [PATCH 28/36] Updated Console Log and comments For RxStatus --- src/components/RequestBox/RequestBox.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index 35deb168..48cd1add 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -386,10 +386,10 @@ export default class RequestBox extends Component { } /** - * Send the NewRxRequestMessage to the Pharmacy Information System (PIMS) + * Send NewRx for new Medication to the Pharmacy Information System (PIMS) */ sendRx = e => { - console.log('sendRx: ' + this.props.pimsUrl); + console.log('Sending NewRx to: ' + this.props.pimsUrl); // build the NewRx Message var newRx = buildNewRxRequest( @@ -397,11 +397,13 @@ export default class RequestBox extends Component { this.state.prefetchedResources.practitioner, this.state.request ); + + console.log('Prepared NewRx:'); console.log(newRx); + const serializer = new XMLSerializer(); - // send the message to the Pharmacy - this.props.consoleLog('Sending Rx to PIMS', types.info); + // Sending NewRx to the Pharmacy fetch(this.props.pimsUrl, { method: 'POST', //mode: 'no-cors', @@ -412,14 +414,11 @@ export default class RequestBox extends Component { body: serializer.serializeToString(newRx) }) .then(response => { - console.log('sendRx response: '); + console.log('Successfully sent NewRx to PIMS'); console.log(response); - this.props.consoleLog('Successfully sent Rx to PIMS', types.info); }) .catch(error => { - console.log('sendRx error: '); - this.props.consoleLog('Server returned error sending Rx to PIMS: ', types.error); - this.props.consoleLog(error.message); + console.log('sendRx Error - unable to send NewRx to PIMS: '); console.log(error); }); }; @@ -451,7 +450,7 @@ export default class RequestBox extends Component { {this.state.patientList instanceof Error ? this.renderError() : Date: Mon, 8 Jan 2024 01:26:15 -0800 Subject: [PATCH 29/36] snackbar notification added --- src/components/RequestBox/RequestBox.js | 35 ++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index 48cd1add..723e9a9b 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -1,13 +1,15 @@ import PersonIcon from '@mui/icons-material/Person'; import { Box, Button, ButtonGroup, Modal } from '@mui/material'; +import MuiAlert from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; import _ from 'lodash'; import React, { Component } from 'react'; import buildNewRxRequest from '../../util/buildScript.2017071.js'; -import { defaultValues, shortNameMap, types } from '../../util/data'; +import { defaultValues, shortNameMap } from '../../util/data'; import { getAge } from '../../util/fhir'; import { retrieveLaunchContext } from '../../util/util'; -import './request.css'; import InProgressFormBox from './InProgressFormBox/InProgressFormBox.js'; +import './request.css'; import PatientSearchBar from './PatientSearchBar/PatientSearchBar.js'; @@ -42,7 +44,8 @@ export default class RequestBox extends Component { display: null, request: {}, gatherCount: 0, - response: {} + response: {}, + open: false }; this.renderRequestResources = this.renderRequestResources.bind(this); @@ -416,6 +419,7 @@ export default class RequestBox extends Component { .then(response => { console.log('Successfully sent NewRx to PIMS'); console.log(response); + this.handleRxResponse(); }) .catch(error => { console.log('sendRx Error - unable to send NewRx to PIMS: '); @@ -431,10 +435,17 @@ export default class RequestBox extends Component { return Object.keys(this.state.patient).length === 0; } + // SnackBar + handleRxResponse = () => this.setState({ open: true }); + + handleClose = () => this.setState({ open: false }); + + render() { const disableSendToCRD = this.isOrderNotSelected() || this.props.loading; const disableSendRx = this.isOrderNotSelected() || this.props.loading; const disableLaunchSmartOnFhir = this.isPatientNotSelected(); + const { open } = this.state; return (
@@ -499,6 +510,24 @@ export default class RequestBox extends Component { Sign Order + + + Success! NewRx Recieved By Pharmacy + +
) : ( From 2846e2b8aa4815cfe930989db6d6a804273b2c89 Mon Sep 17 00:00:00 2001 From: Zach Robin Date: Mon, 8 Jan 2024 03:28:24 -0800 Subject: [PATCH 30/36] Initial Page Layout --- src/components/App.js | 2 + src/components/views/RegisterPage.js | 106 +++++++++++++++++++++ src/components/views/RegisterPageStyle.css | 19 ++++ 3 files changed, 127 insertions(+) create mode 100644 src/components/views/RegisterPage.js create mode 100644 src/components/views/RegisterPageStyle.css diff --git a/src/components/App.js b/src/components/App.js index 52bbf55d..4cc8ba8e 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -7,6 +7,7 @@ import { ThemeProvider } from '@mui/styles'; import Launch from '../containers/Launch'; import Index from '../containers/Index'; import Gateway from '../containers/Gateway/Gateway'; +import RegisterPage from './views/RegisterPage'; const isGhPages = process.env.REACT_APP_GH_PAGES === 'true'; const Router = isGhPages ? HashRouter : BrowserRouter; const redirect = isGhPages ? '/#/index' : '/index'; @@ -16,6 +17,7 @@ const App = () => { } /> } /> + } /> !(c.name === client.name && c.client === client.client) + ); + localStorage.setItem('clients', JSON.stringify(newClients)); + setCurrentClients(newClients); + } + + function submit(event) { + console.log('new selection add to LS'); + const newClients = [...currentClients, { name: fhirUrl, client: clientId }]; + setCurrentClients(newClients); + localStorage.setItem('clients', JSON.stringify(newClients)); + /*if (props.callback) { + event.preventDefault(); + props.callback(); // try launching again + }*/ + return false; + } + + return ( + <> + + + +

Client ID Registration

+ + Request Generator + +



+
+ + { + setClientId(e.target.value); + }} + /> + + Clients must be registered with the FHIR server out of band. + + + + { + setFhirUrl(e.target.value); + }} + /> + + The ISS is the base url of the FHIR server. + + + +
+
+
+

+ + + Existing Client Ids: + + + {currentClients.map((client, index) => { + return ( +
+ + {client.name}: {client.client} + + { + deleteClient(client); + }} + > + + +
+ ); + })} +
+
+ + ); +} diff --git a/src/components/views/RegisterPageStyle.css b/src/components/views/RegisterPageStyle.css new file mode 100644 index 00000000..383364ff --- /dev/null +++ b/src/components/views/RegisterPageStyle.css @@ -0,0 +1,19 @@ +body { + background-color: #F5F5F7; +} + +.container { + max-width:800px; + margin: 0; + position: absolute; + top: 40%; + left: 50%; + transform: translate(-50%, -40%); +} + +.clientIds { + text-align: center; +} +.clientIdList { + text-align: center; +} \ No newline at end of file From 57d98710a48741e857df6aaab67034499e9cf079 Mon Sep 17 00:00:00 2001 From: Zach Robin Date: Tue, 9 Jan 2024 12:41:07 -0800 Subject: [PATCH 31/36] fixed styling --- src/components/views/RegisterPage.js | 2 +- src/components/views/RegisterPageStyle.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/RegisterPage.js b/src/components/views/RegisterPage.js index 4272a47f..1092e44d 100644 --- a/src/components/views/RegisterPage.js +++ b/src/components/views/RegisterPage.js @@ -33,7 +33,7 @@ export default function RegisterPage() { return ( <> - +

Client ID Registration

diff --git a/src/components/views/RegisterPageStyle.css b/src/components/views/RegisterPageStyle.css index 383364ff..ad715639 100644 --- a/src/components/views/RegisterPageStyle.css +++ b/src/components/views/RegisterPageStyle.css @@ -2,7 +2,7 @@ body { background-color: #F5F5F7; } -.container { +.registerContainer { max-width:800px; margin: 0; position: absolute; From a8f3213f8bc21b0b7e3279ac843d7359c9d4d561 Mon Sep 17 00:00:00 2001 From: Zach Robin Date: Wed, 10 Jan 2024 11:41:16 -0800 Subject: [PATCH 32/36] added launch and moved register page in direcotory --- src/components/App.js | 2 +- src/containers/Launch.jsx | 53 +++++++++++++++++-- .../register}/RegisterPage.js | 8 +-- .../register}/RegisterPageStyle.css | 0 4 files changed, 53 insertions(+), 10 deletions(-) rename src/{components/views => containers/register}/RegisterPage.js (96%) rename src/{components/views => containers/register}/RegisterPageStyle.css (100%) diff --git a/src/components/App.js b/src/components/App.js index 4cc8ba8e..0d68b615 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -7,7 +7,7 @@ import { ThemeProvider } from '@mui/styles'; import Launch from '../containers/Launch'; import Index from '../containers/Index'; import Gateway from '../containers/Gateway/Gateway'; -import RegisterPage from './views/RegisterPage'; +import RegisterPage from '../containers/register/registerPage'; const isGhPages = process.env.REACT_APP_GH_PAGES === 'true'; const Router = isGhPages ? HashRouter : BrowserRouter; const redirect = isGhPages ? '/#/index' : '/index'; diff --git a/src/containers/Launch.jsx b/src/containers/Launch.jsx index 5db39527..0e28513e 100644 --- a/src/containers/Launch.jsx +++ b/src/containers/Launch.jsx @@ -2,6 +2,7 @@ import React, { memo, useState, useEffect } from 'react'; import FHIR from 'fhirclient'; import env from 'env-var'; import queryString from 'querystring'; +import RegisterPage from './register/RegisterPage'; const Launch = props => { useEffect(() => { @@ -23,11 +24,53 @@ const Launch = props => { }); }, []); - return ( -
-

Launching...

-
- ); +
+

Launching...

+
+ + useEffect(() => { + smartLaunch(); + }, []); + + const smartLaunch = () => { + let clients = JSON.parse(localStorage.getItem('clients') || '[]'); + if (clients.length === 0) { + const defaultClient = env.get('REACT_APP_CLIENT').asString(); + const defaultIss = env.get('REACT_APP_EHR_BASE').asString(); + if (defaultClient && defaultIss) { + clients = [{ client: defaultClient, name: defaultIss }]; + localStorage.setItem('clients', JSON.stringify(clients)); + } + } + const urlSearchString = window.location.search; + const params = new URLSearchParams(urlSearchString); + const iss = params.get('iss'); + if (iss) { + const storedClient = clients.find(e => { + return e.name == iss; + }); + + if (storedClient) { + // found matching iss + const clientId = storedClient.client; + FHIR.oauth2 + .authorize({ + clientId: clientId, + scope: env.get('REACT_APP_CLIENT_SCOPES').asString(), + redirectUri: '/index' + }) + .catch(e => { + console.log(e); + }); + } else { + setContent(); + } + } else { + setContent(
iss not found
); + } + }; + + return
{content}
; }; export default memo(Launch); diff --git a/src/components/views/RegisterPage.js b/src/containers/register/RegisterPage.js similarity index 96% rename from src/components/views/RegisterPage.js rename to src/containers/register/RegisterPage.js index 1092e44d..55c9ad13 100644 --- a/src/components/views/RegisterPage.js +++ b/src/containers/register/RegisterPage.js @@ -3,9 +3,9 @@ import { Box, Button, Card, CardContent, FormControl, FormHelperText, IconButton import React, { useState } from 'react'; import './RegisterPageStyle.css'; -export default function RegisterPage() { +export default function RegisterPage(props) { const [clientId, setClientId] = useState(''); - const [fhirUrl, setFhirUrl] = useState(''); + const [fhirUrl, setFhirUrl] = useState(props.fhirUrl || ''); const [currentClients, setCurrentClients] = useState( JSON.parse(localStorage.getItem('clients') || '[]') @@ -24,10 +24,10 @@ export default function RegisterPage() { const newClients = [...currentClients, { name: fhirUrl, client: clientId }]; setCurrentClients(newClients); localStorage.setItem('clients', JSON.stringify(newClients)); - /*if (props.callback) { + if (props.callback) { event.preventDefault(); props.callback(); // try launching again - }*/ + } return false; } diff --git a/src/components/views/RegisterPageStyle.css b/src/containers/register/RegisterPageStyle.css similarity index 100% rename from src/components/views/RegisterPageStyle.css rename to src/containers/register/RegisterPageStyle.css From d6a878ae063f33df1d7d35209b08dddb619b9c28 Mon Sep 17 00:00:00 2001 From: Zach Robin Date: Wed, 10 Jan 2024 11:43:12 -0800 Subject: [PATCH 33/36] corrected props and app route --- src/components/App.js | 2 +- src/containers/register/RegisterPage.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/App.js b/src/components/App.js index 0d68b615..cd79e0d8 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -7,7 +7,7 @@ import { ThemeProvider } from '@mui/styles'; import Launch from '../containers/Launch'; import Index from '../containers/Index'; import Gateway from '../containers/Gateway/Gateway'; -import RegisterPage from '../containers/register/registerPage'; +import RegisterPage from '../containers/register/RegisterPage'; const isGhPages = process.env.REACT_APP_GH_PAGES === 'true'; const Router = isGhPages ? HashRouter : BrowserRouter; const redirect = isGhPages ? '/#/index' : '/index'; diff --git a/src/containers/register/RegisterPage.js b/src/containers/register/RegisterPage.js index 55c9ad13..507ec799 100644 --- a/src/containers/register/RegisterPage.js +++ b/src/containers/register/RegisterPage.js @@ -26,7 +26,7 @@ export default function RegisterPage(props) { localStorage.setItem('clients', JSON.stringify(newClients)); if (props.callback) { event.preventDefault(); - props.callback(); // try launching again + props.callback(); // try launching again } return false; } @@ -37,6 +37,7 @@ export default function RegisterPage(props) {

Client ID Registration

+ {props.callback ?
Client ID not found. Please register the client ID.
: ''} Request Generator @@ -71,7 +72,7 @@ export default function RegisterPage(props) {
From 2395127c927ab2aca6906619946d3b17620de8f8 Mon Sep 17 00:00:00 2001 From: Zach Robin Date: Wed, 10 Jan 2024 12:00:07 -0800 Subject: [PATCH 34/36] updated launch --- src/containers/Launch.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/containers/Launch.jsx b/src/containers/Launch.jsx index 0e28513e..bec5ffaa 100644 --- a/src/containers/Launch.jsx +++ b/src/containers/Launch.jsx @@ -5,6 +5,12 @@ import queryString from 'querystring'; import RegisterPage from './register/RegisterPage'; const Launch = props => { + const [content, setContent] = useState( +
+

Launching...

+
+ ); + /* useEffect(() => { // this is a workaround for the fhir client not being able to pull values out of the // hash. Regex will look for different permutations of a hashrouter url /#/launch /#launch #launch /#launch @@ -22,11 +28,7 @@ const Launch = props => { iss: iss, launch: launch }); - }, []); - -
-

Launching...

-
+ }, []); */ useEffect(() => { smartLaunch(); From c4c24c2f16e394b8f0894576434542297367da74 Mon Sep 17 00:00:00 2001 From: Zach Robin Date: Wed, 10 Jan 2024 12:29:23 -0800 Subject: [PATCH 35/36] formatting --- src/components/App.js | 13 +++--- src/containers/Launch.jsx | 24 +---------- src/containers/register/RegisterPage.js | 53 ++++++++++++++++--------- 3 files changed, 42 insertions(+), 48 deletions(-) diff --git a/src/components/App.js b/src/components/App.js index cd79e0d8..0c514e36 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -1,13 +1,12 @@ -import React from 'react'; -import { BrowserRouter, HashRouter, Routes, Route } from 'react-router-dom'; -import RequestBuilder from '../containers/RequestBuilder'; -import PatientPortal from '../containers/PatientPortal'; -import theme from '../containers/styles/theme'; import { ThemeProvider } from '@mui/styles'; -import Launch from '../containers/Launch'; -import Index from '../containers/Index'; +import React from 'react'; +import { BrowserRouter, HashRouter, Route, Routes } from 'react-router-dom'; import Gateway from '../containers/Gateway/Gateway'; +import Index from '../containers/Index'; +import Launch from '../containers/Launch'; +import PatientPortal from '../containers/PatientPortal'; import RegisterPage from '../containers/register/RegisterPage'; +import theme from '../containers/styles/theme'; const isGhPages = process.env.REACT_APP_GH_PAGES === 'true'; const Router = isGhPages ? HashRouter : BrowserRouter; const redirect = isGhPages ? '/#/index' : '/index'; diff --git a/src/containers/Launch.jsx b/src/containers/Launch.jsx index bec5ffaa..4021ef82 100644 --- a/src/containers/Launch.jsx +++ b/src/containers/Launch.jsx @@ -1,7 +1,6 @@ -import React, { memo, useState, useEffect } from 'react'; -import FHIR from 'fhirclient'; import env from 'env-var'; -import queryString from 'querystring'; +import FHIR from 'fhirclient'; +import React, { memo, useEffect, useState } from 'react'; import RegisterPage from './register/RegisterPage'; const Launch = props => { @@ -10,25 +9,6 @@ const Launch = props => {

Launching...

); - /* - useEffect(() => { - // this is a workaround for the fhir client not being able to pull values out of the - // hash. Regex will look for different permutations of a hashrouter url /#/launch /#launch #launch /#launch - const params = queryString.parse((window.location.hash || '').replace(/\/?#\/?launch\?/, '')); - - // if these are null then the client will pull them out of the browsers query string - // so we don't need to do that here. - const iss = params.iss; - const launch = params.launch; - - FHIR.oauth2.authorize({ - clientId: env.get('REACT_APP_CLIENT').asString(), - scope: env.get('REACT_APP_CLIENT_SCOPES').asString(), - redirectUri: props.redirect, - iss: iss, - launch: launch - }); - }, []); */ useEffect(() => { smartLaunch(); diff --git a/src/containers/register/RegisterPage.js b/src/containers/register/RegisterPage.js index 507ec799..a38d4327 100644 --- a/src/containers/register/RegisterPage.js +++ b/src/containers/register/RegisterPage.js @@ -1,5 +1,15 @@ import CloseIcon from '@mui/icons-material/Close'; -import { Box, Button, Card, CardContent, FormControl, FormHelperText, IconButton, TextField, Typography } from '@mui/material'; +import { + Box, + Button, + Card, + CardContent, + FormControl, + FormHelperText, + IconButton, + TextField, + Typography +} from '@mui/material'; import React, { useState } from 'react'; import './RegisterPageStyle.css'; @@ -13,7 +23,7 @@ export default function RegisterPage(props) { function deleteClient(client) { const newClients = currentClients.filter( - (c) => !(c.name === client.name && c.client === client.client) + c => !(c.name === client.name && c.client === client.client) ); localStorage.setItem('clients', JSON.stringify(newClients)); setCurrentClients(newClients); @@ -38,54 +48,59 @@ export default function RegisterPage(props) {

Client ID Registration

{props.callback ?
Client ID not found. Please register the client ID.
: ''} - + Request Generator -



-
- +

+

+ + { setClientId(e.target.value); }} /> - + Clients must be registered with the FHIR server out of band. - + { setFhirUrl(e.target.value); }} /> - + The ISS is the base url of the FHIR server. -


- - + + Existing Client Ids: {currentClients.map((client, index) => { return ( -
+
{client.name}: {client.client} From 14d6ada215271841918b1e962641375510d31a2d Mon Sep 17 00:00:00 2001 From: Patrick LaRocque Date: Thu, 11 Jan 2024 16:04:43 -0500 Subject: [PATCH 36/36] Fix deployment to github pages and launch from meld. Fix crash when sending to rems-admin if rems-admin does not respond. --- src/components/App.js | 2 +- .../InProgressFormBox/InProgressFormBox.js | 2 +- src/components/RequestBox/RequestBox.js | 10 ++++++---- src/containers/Launch.jsx | 12 +++++++++--- src/containers/RequestBuilder.js | 2 +- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/components/App.js b/src/components/App.js index 0c514e36..dd43cadf 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -9,7 +9,7 @@ import RegisterPage from '../containers/register/RegisterPage'; import theme from '../containers/styles/theme'; const isGhPages = process.env.REACT_APP_GH_PAGES === 'true'; const Router = isGhPages ? HashRouter : BrowserRouter; -const redirect = isGhPages ? '/#/index' : '/index'; +const redirect = isGhPages ? '/request-generator/#/index' : '/index'; const App = () => { return ( diff --git a/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js b/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js index 293003e8..a9d90891 100644 --- a/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js +++ b/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js @@ -4,7 +4,7 @@ import './InProgressFormBoxStyle.css'; export default function InProgressFormBox(props) { return ( - props.qrResponse.questionnaire ? ( + props.qrResponse?.questionnaire ? ( In Progress Form diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index b71ab0bd..408367a2 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -385,10 +385,12 @@ export default class RequestBox extends Component {
- + {Object.keys(this.props.response).length ? + + : }