Skip to content

Commit

Permalink
MHV-65059 Error handling for BB download (#33535)
Browse files Browse the repository at this point in the history
* MHV-65059 Don't overwrite data if key is missing from the action payload

* MHV-65059 Modified action to only call necessary APIs

* MHV-65059 Harmonized flow for generating BB PDF

* MHV-65059 Revert test data

* MHV-65059 Started adding error-handling

* MHV-65059 More work on error handling

* MHV-65059 Fixed mismatched field

* MHV-65059 Added error alert for failed APIs

* MHV-65059 Fixed unit tests

* MHV-65059 Added a test for failed calls

* MHV-65059 Fixed URL for appointments
  • Loading branch information
mmoyer-va authored Dec 12, 2024
1 parent 764af7e commit fb77008
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 105 deletions.
81 changes: 47 additions & 34 deletions src/applications/mhv-medical-records/actions/blueButtonReport.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,58 @@ import {
getAppointments,
} from '../api/MrApi';
import { Actions } from '../util/actionTypes';
import * as Constants from '../util/constants';
import { addAlert } from './alerts';

export const clearFailedList = domain => dispatch => {
dispatch({ type: Actions.BlueButtonReport.CLEAR_FAILED, payload: domain });
};

export const getBlueButtonReportData = (options = {}) => async dispatch => {
try {
const fetchMap = {
labs: getLabsAndTests,
notes: getNotes,
vaccines: getVaccineList,
allergies: getAllergies,
conditions: getConditions,
vitals: getVitalsList,
radiology: getMhvRadiologyTests,
medications: getMedications,
appointments: getAppointments,
demographics: getDemographicInfo,
militaryService: getMilitaryService,
patient: getPatient,
};
const fetchMap = {
labsAndTests: getLabsAndTests,
notes: getNotes,
vaccines: getVaccineList,
allergies: getAllergies,
conditions: getConditions,
vitals: getVitalsList,
radiology: getMhvRadiologyTests,
medications: getMedications,
appointments: getAppointments,
demographics: getDemographicInfo,
militaryService: getMilitaryService,
patient: getPatient,
};

const promises = Object.entries(fetchMap)
.filter(([key]) => options[key]) // Only include enabled fetches
.map(([key, fetchFn]) => fetchFn().then(response => ({ key, response })));
const promises = Object.entries(fetchMap)
.filter(([key]) => options[key]) // Only include enabled fetches
.map(([key, fetchFn]) =>
fetchFn()
.then(response => ({ key, response }))
.catch(error => {
const newError = new Error(error);
newError.key = key;
throw newError;
}),
);

const results = await Promise.all(promises);
const results = await Promise.allSettled(promises);

results.forEach(({ key, response }) => {
results.forEach(({ status, value, reason }) => {
if (status === 'fulfilled') {
const { key, response } = value;
switch (key) {
case 'labs':
case 'labsAndTests':
dispatch({
type: Actions.LabsAndTests.GET_LIST,
labsAndTestsResponse: response,
});
break;
// TODO: Handle this with labs
case 'radiology':
dispatch({
type: Actions.LabsAndTests.GET_LIST,
radiologyResponse: response,
});
break;
case 'notes':
dispatch({
type: Actions.CareSummariesAndNotes.GET_LIST,
Expand Down Expand Up @@ -77,12 +95,6 @@ export const getBlueButtonReportData = (options = {}) => async dispatch => {
response,
});
break;
case 'radiology':
dispatch({
type: Actions.LabsAndTests.GET_LIST,
radiologyResponse: response,
});
break;
case 'medications':
case 'appointments':
case 'demographics':
Expand All @@ -96,9 +108,10 @@ export const getBlueButtonReportData = (options = {}) => async dispatch => {
default:
break;
}
});
} catch (error) {
dispatch(addAlert(Constants.ALERT_TYPE_ERROR, error));
throw error;
}
} else {
// Handle rejected promises
const { key } = reason;
dispatch({ type: Actions.BlueButtonReport.ADD_FAILED, payload: key });
}
});
};
22 changes: 11 additions & 11 deletions src/applications/mhv-medical-records/api/MrApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const getRefreshStatus = () => {
});
};

export const getLabsAndTests = () => {
export const getLabsAndTests = async () => {
return apiRequest(`${apiBasePath}/medical_records/labs_and_tests`, {
headers,
});
Expand Down Expand Up @@ -62,7 +62,7 @@ export const getBbmiNotificationStatus = () => {
});
};

export const getMhvRadiologyTests = () => {
export const getMhvRadiologyTests = async () => {
return apiRequest(`${apiBasePath}/medical_records/radiology`, {
headers,
});
Expand All @@ -83,7 +83,7 @@ export const getMhvRadiologyDetails = async id => {
return findMatchingPhrAndCvixStudies(id, phrResponse, cvixResponse);
};

export const getNotes = () => {
export const getNotes = async () => {
return apiRequest(`${apiBasePath}/medical_records/clinical_notes`, {
headers,
});
Expand All @@ -95,7 +95,7 @@ export const getNote = id => {
});
};

export const getVitalsList = () => {
export const getVitalsList = async () => {
return apiRequest(`${apiBasePath}/medical_records/vitals`, {
headers,
});
Expand Down Expand Up @@ -158,7 +158,7 @@ export const getAcceleratedAllergy = id => {
* Get a patient's vaccines
* @returns list of patient's vaccines in FHIR format
*/
export const getVaccineList = () => {
export const getVaccineList = async () => {
return apiRequest(`${apiBasePath}/medical_records/vaccines`, {
headers,
});
Expand Down Expand Up @@ -208,7 +208,7 @@ export const getImageRequestStatus = () => {
* Get a patient's medications
* @returns list of patient's medications
*/
export const getMedications = () => {
export const getMedications = async () => {
return apiRequest(`${apiBasePath}/prescriptions`, {
headers,
});
Expand All @@ -218,7 +218,7 @@ export const getMedications = () => {
* Get a patient's appointments
* @returns list of patient's appointments
*/
export const getAppointments = () => {
export const getAppointments = async () => {
const now = new Date();
const startDate = formatISO(now);
const beginningOfTime = new Date(0);
Expand All @@ -227,7 +227,7 @@ export const getAppointments = () => {
'&statuses[]=booked&statuses[]=arrived&statuses[]=fulfilled&statuses[]=cancelled';
const params = `_include=facilities,clinics&start=${startDate}&end=${endDate}${statusParams}`;

return apiRequest(`${apiBasePath}/vaos/v2/appointments?${params}`, {
return apiRequest(`${environment.API_URL}/vaos/v2/appointments?${params}`, {
headers,
});
};
Expand All @@ -236,7 +236,7 @@ export const getAppointments = () => {
* Get a patient's demographic info
* @returns patient's demographic info
*/
export const getDemographicInfo = () => {
export const getDemographicInfo = async () => {
return apiRequest(`${apiBasePath}/medical_records/patient/demographic`, {
headers,
});
Expand All @@ -247,7 +247,7 @@ export const getDemographicInfo = () => {
* Get a patient's military service info
* @returns patient's military service info
*/
export const getMilitaryService = () => {
export const getMilitaryService = async () => {
return apiRequest(`${apiBasePath}/medical_records/military_service`, {
textHeaders,
});
Expand All @@ -258,7 +258,7 @@ export const getMilitaryService = () => {
* Get a patient's account summary (treatment facilities)
* @returns patient profile including a list of patient's treatment facilities
*/
export const getPatient = () => {
export const getPatient = async () => {
return apiRequest(`${apiBasePath}/medical_records/patient`, {
headers,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const DownloadFileType = props => {
const accountSummary = useSelector(
state => state.mr.blueButton.accountSummary,
);
const failedDomains = useSelector(state => state.mr.blueButton.failedDomains);

const recordFilter = useSelector(state => state.mr.downloads?.recordFilter);
const dateFilter = useSelector(state => state.mr.downloads?.dateFilter);
Expand Down Expand Up @@ -102,8 +103,29 @@ const DownloadFileType = props => {
accountSummary,
};

// Check if all domains in the recordFilter are truthy
return recordFilter?.every(filter => !!dataMap[filter]);
// Map the recordFilter keys to the option list
const optionsMap = {
labTests: 'labsAndTests',
careSummaries: 'notes',
vaccines: 'vaccines',
allergies: 'allergies',
conditions: 'conditions',
vitals: 'vitals',
medications: 'medications',
upcomingAppts: 'appointments',
pastAppts: 'appointments',
demographics: 'demographics',
militaryService: 'militaryService',
accountSummary: 'patient',
};

// Check if all domains in the recordFilter were fetched or failed
return recordFilter?.every(filter => {
const optionDomain = optionsMap[filter];
const isFetched = !!dataMap[filter];
const hasFailed = failedDomains.includes(optionDomain);
return isFetched || hasFailed;
});
},
[
labsAndTests,
Expand All @@ -117,14 +139,15 @@ const DownloadFileType = props => {
demographics,
militaryService,
accountSummary,
failedDomains,
recordFilter,
],
);

useEffect(
() => {
const options = {
labs: recordFilter?.includes('labTests'),
labsAndTests: recordFilter?.includes('labTests'),
notes: recordFilter?.includes('careSummaries'),
vaccines: recordFilter?.includes('vaccines'),
allergies:
Expand All @@ -138,6 +161,7 @@ const DownloadFileType = props => {
recordFilter?.includes('pastAppts'),
demographics: recordFilter?.includes('demographics'),
militaryService: recordFilter?.includes('militaryService'),
patient: true,
};

if (!isDataFetched) {
Expand All @@ -151,30 +175,40 @@ const DownloadFileType = props => {
() => {
if (isDataFetched) {
return {
labsAndTests: recordFilter?.includes('labTests')
? labsAndTests.filter(rec => filterByDate(rec.sortDate))
: null,
notes: recordFilter?.includes('careSummaries')
? notes.filter(rec => filterByDate(rec.sortByDate))
: null,
vaccines: recordFilter?.includes('vaccines')
? vaccines.filter(rec => filterByDate(rec.date))
: null,
labsAndTests:
labsAndTests && recordFilter?.includes('labTests')
? labsAndTests.filter(rec => filterByDate(rec.sortDate))
: null,
notes:
notes && recordFilter?.includes('careSummaries')
? notes.filter(rec => filterByDate(rec.sortByDate))
: null,
vaccines:
vaccines && recordFilter?.includes('vaccines')
? vaccines.filter(rec => filterByDate(rec.date))
: null,
allergies:
recordFilter?.includes('allergies') ||
recordFilter?.includes('medications')
allergies &&
(recordFilter?.includes('allergies') ||
recordFilter?.includes('medications'))
? allergies
: null,
conditions: recordFilter?.includes('conditions') ? conditions : null,
vitals: recordFilter?.includes('vitals')
? vitals.filter(rec => filterByDate(rec.date))
: null,
medications: recordFilter?.includes('medications')
? medications.filter(rec => filterByDate(rec.lastFilledOn))
: null,
conditions:
conditions && recordFilter?.includes('conditions')
? conditions
: null,
vitals:
vitals && recordFilter?.includes('vitals')
? vitals.filter(rec => filterByDate(rec.date))
: null,
medications:
medications && recordFilter?.includes('medications')
? medications.filter(rec => filterByDate(rec.lastFilledOn))
: null,
appointments:
recordFilter?.includes('upcomingAppts') ||
recordFilter?.includes('pastAppts')
appointments &&
(recordFilter?.includes('upcomingAppts') ||
recordFilter?.includes('pastAppts'))
? appointments.filter(
rec =>
filterByDate(rec.date) &&
Expand All @@ -183,12 +217,14 @@ const DownloadFileType = props => {
(recordFilter.includes('pastAppts') && !rec.isUpcoming)),
)
: null,
demographics: recordFilter?.includes('demographics')
? demographics
: null,
militaryService: recordFilter?.includes('militaryService')
? militaryService
: null,
demographics:
demographics && recordFilter?.includes('demographics')
? demographics
: null,
militaryService:
militaryService && recordFilter?.includes('militaryService')
? militaryService
: null,
accountSummary,
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';

const MissingRecordsError = ({ recordTypes }) => {
if (!Array.isArray(recordTypes) || recordTypes.length === 0) {
return <></>;
}
return (
<va-alert
status="error"
visible
aria-live="polite"
data-testid="missing-records-error-alert"
>
<h3 id="track-your-status-on-mobile" slot="headline">
We can’t include certain records in your VA Blue Button report right now
</h3>
<p>
We’re sorry. There’s a problem with our system. The report you just
downloaded doesn’t include these records:
</p>
<ul>
{recordTypes.map(recordType => (
<li key={recordType}>{recordType}</li>
))}
</ul>
<p>
Try downloading these records again later. If it still doesn’t work,
call us at <va-telephone contact="8773270022" /> (
<va-telephone tty contact="711" />
). We’re here Monday through Friday, 8:00 a.m. to 8:00 p.m. ET.
</p>
</va-alert>
);
};

export default MissingRecordsError;

MissingRecordsError.propTypes = {
recordTypes: PropTypes.array,
};
Loading

0 comments on commit fb77008

Please sign in to comment.