Skip to content

Commit

Permalink
Merge pull request #134 from mcode/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
smalho01 authored Aug 12, 2024
2 parents f341795 + 1921527 commit 88a4aba
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 93 deletions.
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ VITE_PIMS_SERVER = http://localhost:5051/doctorOrders/api/addRx
VITE_PUBLIC_KEYS = http://localhost:3000/request-generator/.well-known/jwks.json
VITE_REALM = ClientFhirServer
VITE_RESPONSE_EXPIRATION_DAYS = 30
VITE_SERVER = http://localhost:8090
VITE_SMART_LAUNCH_URL = http://localhost:4040/
VITE_URL = http://localhost:3000
VITE_USER = alice
VITE_HOOK_TO_SEND = patient-view
VITE_URL_FILTER = http://localhost:3000/*
VITE_USE_INTERMEDIARY = false
VITE_INTERMEDIARY = http://localhost:3003
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ Following are a list of modifiable paths:
| VITE_PUBLIC_KEYS | `http://localhost:3000/request-generator/.well-known/jwks.json` | The endpoint which contains the public keys for authentication with the REMS admin. Should be changed if the keys are moved elsewhere. |
| VITE_REALM | `ClientFhirServer` | The Keycloak realm to use. Only relevant is using Keycloak as an authentication server. This only affects direct logins like through the Patient Portal, not SMART launches like opening the app normally. |
| VITE_RESPONSE_EXPIRATION_DAYS | `30` | The number of days old a Questionnaire Response can be before it is ignored and filtered out. This ensures the patient search excludes outdated or obsolete prior sessions from creating clutter. |
| VITE_SERVER | `http://localhost:8090` | The default base URL of the CDS service. Typically this will be the base url of the REMS Admin. |
| VITE_SMART_LAUNCH_URL | `http://localhost:4040/` | The base url of the SMART app. This is used for opening the app directly, rather than doing an EHR SMART launch. |
| VITE_URL | `http://localhost:3000` | The base url of this app. Should be modified if the port or domain change. |
| VITE_USER | `alice` | The default user to login as when opening the app. |
| VITE_USER | `alice` | The default user to login as when opening the app. |
| VITE_USE_INTERMEDIARY | false | When true, the app will send all CDS Hooks and REMS ETASU check calls to the intermediary defined in VITE_INTERMEDIARY. |
| VITE_INTERMEDIARY | `http://localhost:3030` | The base url of the intermediary. |
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SettingsContext } from '../../../containers/ContextProvider/SettingsPro
import { EtasuStatusComponent } from '../../EtasuStatus/EtasuStatusComponent';
import axios from 'axios';
import { createMedicationFromMedicationRequest } from '../../../util/fhir';
import { standardsBasedGetEtasu } from '../../../util/util';
import { standardsBasedGetEtasu, getMedicationSpecificEtasuUrl } from '../../../util/util';

const NotificationsSection = () => {
const [globalState, _] = useContext(SettingsContext);
Expand Down Expand Up @@ -49,7 +49,7 @@ const NotificationsSection = () => {
const getAllEtasu = () => {
medications.forEach(medication => {
const body = makeBody(medication);
const standardEtasuUrl = `${globalState.remsAdminServer}/4_0_0/GuidanceResponse/$rems-etasu`;
const standardEtasuUrl = getMedicationSpecificEtasuUrl(medication?.code, globalState);
standardsBasedGetEtasu(standardEtasuUrl, body, compileResponses);
});
};
Expand Down Expand Up @@ -81,6 +81,7 @@ const NotificationsSection = () => {
display={display}
remsAdminResponseInit={remsCase}
data={remsCase.body}
medication={remsCase.body.parameter[1]?.resource}
/>
);
})}
Expand Down
4 changes: 2 additions & 2 deletions src/components/DisplayBox/DisplayBox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ const DisplayBox = props => {
card.links.map((link, ind) => {
if (link.type === 'smart') {
linksSection.push(
<ListItem sx={{ marginLeft: '-12px' }}>
<ListItem key={ind} sx={{ marginLeft: '-12px' }}>
<Button
key={ind}
variant="outlined"
Expand Down Expand Up @@ -318,7 +318,7 @@ const DisplayBox = props => {
card.links.map((link, ind) => {
if (link.type === 'absolute') {
documentationSection.push(
<ListItem>
<ListItem key={ind}>
<Box key={ind}>
<Button
variant="text"
Expand Down
11 changes: 7 additions & 4 deletions src/components/EtasuStatus/EtasuStatus.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState, useEffect, useContext } from 'react';
import { SettingsContext } from '../../containers/ContextProvider/SettingsProvider.jsx';
import { EtasuStatusComponent } from './EtasuStatusComponent.jsx';
import { standardsBasedGetEtasu } from '../../util/util.js';
import { createMedicationFromMedicationRequest, getDrugCodeableConceptFromMedicationRequest } from '../../util/fhir.js';
import { standardsBasedGetEtasu, getMedicationSpecificEtasuUrl } from '../../util/util.js';
import { createMedicationFromMedicationRequest } from '../../util/fhir.js';

// converts code into etasu for the component to render
// simplifies usage for applications that only know the code, not the case they want to display
Expand All @@ -12,6 +12,7 @@ export const EtasuStatus = props => {
const { code, request } = props;
const [remsAdminResponse, setRemsAdminResponse] = useState({});
const [etasuData, setEtasuData] = useState({});
const [medication, setMedication] = useState({});
const [display, setDisplay] = useState('');

useEffect(() => {
Expand All @@ -22,9 +23,10 @@ export const EtasuStatus = props => {
const getEtasuStatus = medication => {
const body = makeBody(medication);
setEtasuData(body);
setMedication(medication);
const display = body.parameter[1]?.resource.code?.coding[0].display;
setDisplay(display);
const standardEtasuUrl = `${globalState.remsAdminServer}/4_0_0/GuidanceResponse/$rems-etasu`;
const standardEtasuUrl = getMedicationSpecificEtasuUrl(medication?.code, globalState);
standardsBasedGetEtasu(standardEtasuUrl, body, setRemsAdminResponse);
};

Expand All @@ -47,11 +49,12 @@ export const EtasuStatus = props => {

return (
<>
{remsAdminResponse.contained ? (
{remsAdminResponse?.contained ? (
<EtasuStatusComponent
remsAdminResponseInit={remsAdminResponse}
data={etasuData}
display={display}
medication={medication}
/>
) : (
''
Expand Down
6 changes: 3 additions & 3 deletions src/components/EtasuStatus/EtasuStatusComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { EtasuStatusModal } from './EtasuStatusModal.jsx';
import { useState, useEffect, useContext } from 'react';
import { Card, Typography } from '@mui/material';
import { SettingsContext } from '../../containers/ContextProvider/SettingsProvider.jsx';
import { standardsBasedGetEtasu } from '../../util/util.js';
import { standardsBasedGetEtasu, getMedicationSpecificEtasuUrl } from '../../util/util.js';

export const EtasuStatusComponent = props => {
const [globalState, _] = useContext(SettingsContext);

const { remsAdminResponseInit, data, display } = props;
const { remsAdminResponseInit, data, display, medication } = props;

const [remsAdminResponse, setRemsAdminResponse] = useState(remsAdminResponseInit);
const [lastCheckedEtasuTime, setLastCheckedEtasuTime] = useState(0);
Expand All @@ -28,7 +28,7 @@ export const EtasuStatusComponent = props => {

const refreshEtasu = () => {
if (remsAdminResponse) {
const standardEtasuUrl = `${globalState.remsAdminServer}/4_0_0/GuidanceResponse/$rems-etasu`;
const standardEtasuUrl = getMedicationSpecificEtasuUrl(medication?.code, globalState);
standardsBasedGetEtasu(standardEtasuUrl, data, setRemsAdminResponse);
setLastCheckedEtasuTime(Date.now());
}
Expand Down
36 changes: 24 additions & 12 deletions src/components/RequestBox/RequestBox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@ import buildNewRxRequest from '../../util/buildScript.2017071.js';
import MuiAlert from '@mui/material/Alert';
import Snackbar from '@mui/material/Snackbar';
import { shortNameMap, ORDER_SIGN, PATIENT_VIEW } from '../../util/data.js';
import { getAge, createMedicationDispenseFromMedicationRequest, createMedicationFromMedicationRequest } from '../../util/fhir.js';
import { retrieveLaunchContext, prepPrefetch } from '../../util/util.js';
import {
getAge,
createMedicationDispenseFromMedicationRequest,
createMedicationFromMedicationRequest,
getDrugCodeableConceptFromMedicationRequest
} from '../../util/fhir.js';
import {
retrieveLaunchContext,
prepPrefetch,
getMedicationSpecificEtasuUrl
} from '../../util/util.js';
import './request.css';
import axios from 'axios';

Expand All @@ -17,7 +26,7 @@ const RequestBox = props => {
response: {},
submittedRx: false
});
const [globalState,] = useContext(SettingsContext);
const [globalState] = useContext(SettingsContext);

const {
prefetchedResources,
Expand Down Expand Up @@ -56,7 +65,6 @@ const RequestBox = props => {
}
}, [prefetchCompleted]);


const renderPatientInfo = () => {
if (Object.keys(patient).length === 0) {
return <div className="demographics"></div>;
Expand Down Expand Up @@ -214,26 +222,30 @@ const RequestBox = props => {
*/
const sendRx = async () => {
console.log('Sending NewRx to: ' + pimsUrl);
console.log('Getting auth number ')
console.log('Getting auth number ');
const medication = createMedicationFromMedicationRequest(request);
const body = makeBody(medication);
const standardEtasuUrl = `${globalState.remsAdminServer}/4_0_0/GuidanceResponse/$rems-etasu`;
const standardEtasuUrl = getMedicationSpecificEtasuUrl(
getDrugCodeableConceptFromMedicationRequest(request),
globalState
);
let authNumber = '';
await axios({
method: 'post',
url: standardEtasuUrl,
data: body
}).then(
response => {
if (response.data.parameter[0].resource && response.data.parameter[0].resource.contained) {
response.data.parameter[0].resource?.contained[0]?.parameter.map(metRequirements => {
}).then(response => {
if (
response.data.parameter?.[0].resource &&
response.data.parameter?.[0].resource.contained
) {
response.data.parameter?.[0].resource?.contained[0]?.parameter.map(metRequirements => {
if (metRequirements.name === 'auth_number') {
authNumber = metRequirements.valueString;
}
});
}
}
);
});

// build the NewRx Message
var newRx = buildNewRxRequest(
Expand Down
20 changes: 11 additions & 9 deletions src/components/RequestDashboard/SettingsSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ import AddIcon from '@mui/icons-material/Add';
import env from 'env-var';
import FHIR from 'fhirclient';

import { headerDefinitions, medicationRequestToRemsAdmins, ORDER_SIGN, ORDER_SELECT, PATIENT_VIEW, ENCOUNTER_START } from '../../util/data';
import { headerDefinitions, medicationRequestToRemsAdmins, ORDER_SIGN, ORDER_SELECT, PATIENT_VIEW, ENCOUNTER_START, REMS_ETASU } from '../../util/data';
import { actionTypes, initialState } from '../../containers/ContextProvider/reducer';
import { SettingsContext } from '../../containers/ContextProvider/SettingsProvider';

const CDS_HOOKS = [ORDER_SIGN, ORDER_SELECT, PATIENT_VIEW, ENCOUNTER_START];
const ENDPOINT = [ORDER_SIGN, ORDER_SELECT, PATIENT_VIEW, ENCOUNTER_START, REMS_ETASU];

const SettingsSection = props => {
const [state, dispatch] = React.useContext(SettingsContext);
Expand Down Expand Up @@ -299,13 +299,14 @@ const SettingsSection = props => {
maxHeight: 440
}}
>
{!state['useIntermediary'] && (
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow sx={{ th: { fontWeight: 'bold' } }}>
<TableCell width={500}>Medication Display</TableCell>
<TableCell width={200}>Medication RxNorm Code</TableCell>
<TableCell width={200}>CDS Hook</TableCell>
<TableCell width={500}>REMS Admin Endpoint</TableCell>
<TableCell width={200}>Hook / Endpoint</TableCell>
<TableCell width={500}>REMS Admin URL</TableCell>
{/* This empty TableCell corresponds to the add and delete
buttons. It is used to fill up the sticky header which
will appear over the gray/white table rows. */}
Expand Down Expand Up @@ -348,19 +349,19 @@ const SettingsSection = props => {
<Select
labelId="dropdown-label"
id="dropdown"
value={row.hook}
value={row.endpointType}
onChange={event =>
dispatch({
type: actionTypes.updateCdsHookSetting,
settingId: key,
value: { hook: event.target.value }
value: { endpointType: event.target.value }
})
}
sx={{ width: '100%' }}
>
{CDS_HOOKS.map(hook => (
<MenuItem key={hook} value={hook}>
{hook}
{ENDPOINT.map(endpointType => (
<MenuItem key={endpointType} value={endpointType}>
{endpointType}
</MenuItem>
))}
</Select>
Expand Down Expand Up @@ -408,6 +409,7 @@ const SettingsSection = props => {
})}
</TableBody>
</Table>
)}
</TableContainer>
</Grid>

Expand Down
10 changes: 5 additions & 5 deletions src/containers/ContextProvider/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const getNewStateWithNewCdsHookSetting = (state, settingId) => {
newState.medicationRequestToRemsAdmins[uuidv4()] = {
rxnorm: 'Fill out Medication RxNorm Code',
display: 'Fill out Medication Display Name',
hook: ORDER_SIGN,
endpointType: ORDER_SIGN,
remsAdmin: 'REMS Admin URL for CDS Hook'
};

Expand Down Expand Up @@ -108,10 +108,10 @@ export const initialState = (() => {
});

medicationRequestToRemsAdmins.forEach(row => {
const { rxnorm, display, hookEndpoints } = row;
hookEndpoints.forEach(({ hook, remsAdmin }) => {
const key = `${rxnorm}_${hook}`;
state.medicationRequestToRemsAdmins[key] = { rxnorm, display, hook, remsAdmin };
const { rxnorm, display, endpoints } = row;
endpoints.forEach(({ endpointType, remsAdmin }) => {
const key = `${rxnorm}_${endpointType}`;
state.medicationRequestToRemsAdmins[key] = { rxnorm, display, endpointType, remsAdmin };
});
});
return state;
Expand Down
6 changes: 3 additions & 3 deletions src/containers/RequestBuilder.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import RequestBox from '../components/RequestBox/RequestBox.jsx';
import buildRequest from '../util/buildRequest.js';
import { types, PATIENT_VIEW } from '../util/data.js';
import { createJwt } from '../util/auth.js';
import { getMedicationSpecificRemsAdminUrl, prepPrefetch } from '../util/util.js';
import { getMedicationSpecificCdsHooksUrl, prepPrefetch } from '../util/util.js';

import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
Expand Down Expand Up @@ -121,7 +121,7 @@ const RequestBuilder = props => {
let remsAdminUrls = [];
// get all the remsAdminUrl for each MedicationRequest
state.medicationRequests?.data?.forEach(request => {
const remsAdminUrl = getMedicationSpecificRemsAdminUrl(request, globalState, hook);
const remsAdminUrl = getMedicationSpecificCdsHooksUrl(request, globalState, hook);
if (remsAdminUrl) {
remsAdminUrls.push(remsAdminUrl);
}
Expand All @@ -138,7 +138,7 @@ const RequestBuilder = props => {
console.log('Initiating form submission ', types.info);
let remsAdminUrl = null;
if (request) {
remsAdminUrl = getMedicationSpecificRemsAdminUrl(request, globalState, hook);
remsAdminUrl = getMedicationSpecificCdsHooksUrl(request, globalState, hook);
sendHook(prefetch, request, patient, hook, remsAdminUrl);
} else {
// get all MedicationRequests for the patient, then continue
Expand Down
Loading

0 comments on commit 88a4aba

Please sign in to comment.