Skip to content

Commit

Permalink
MHV-65380: Appointments API call being fed dates to filter by (#33669)
Browse files Browse the repository at this point in the history
* MHV-65380: Appointments API call being fed dates to filter by

* MHV-65380: Invalidate appointments when selected date changes

* MHV-65380: Fixed and added unit tests
  • Loading branch information
mattwrightva authored Dec 19, 2024
1 parent f6f6d07 commit 5b12bcc
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 39 deletions.
36 changes: 31 additions & 5 deletions src/applications/mhv-medical-records/actions/blueButtonReport.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { formatISO, subYears } from 'date-fns';
import {
getLabsAndTests,
getNotes,
Expand All @@ -18,7 +19,10 @@ export const clearFailedList = domain => dispatch => {
dispatch({ type: Actions.BlueButtonReport.CLEAR_FAILED, payload: domain });
};

export const getBlueButtonReportData = (options = {}) => async dispatch => {
export const getBlueButtonReportData = (
options = {},
dateFilter,
) => async dispatch => {
const fetchMap = {
labsAndTests: getLabsAndTests,
notes: getNotes,
Expand All @@ -36,15 +40,37 @@ export const getBlueButtonReportData = (options = {}) => async dispatch => {

const promises = Object.entries(fetchMap)
.filter(([key]) => options[key]) // Only include enabled fetches
.map(([key, fetchFn]) =>
fetchFn()
.map(([key, fetchFn]) => {
if (key === 'appointments') {
let fromDate;
let toDate;
if (dateFilter.option === 'any') {
const currentDate = new Date();
const dateTwoYearsAgo = subYears(currentDate, 2);
const farFutureDate = new Date(2099, 0, 1); // January 1, 2099
fromDate = formatISO(dateTwoYearsAgo);
toDate = formatISO(farFutureDate);
} else {
fromDate = formatISO(new Date(dateFilter.fromDate));
toDate = formatISO(new Date(dateFilter.toDate));
}

return fetchFn(fromDate, toDate)
.then(response => ({ key, response }))
.catch(error => {
const newError = new Error(error);
newError.key = key;
throw newError;
});
}
return fetchFn()
.then(response => ({ key, response }))
.catch(error => {
const newError = new Error(error);
newError.key = key;
throw newError;
}),
);
});
});

const results = await Promise.allSettled(promises);

Expand Down
1 change: 1 addition & 0 deletions src/applications/mhv-medical-records/actions/downloads.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const updateReportDateRange = (
toDate,
},
});
dispatch({ type: Actions.BlueButtonReport.CLEAR_APPOINTMENTS });
};

export const updateReportRecordType = selectedTypes => async dispatch => {
Expand Down
9 changes: 2 additions & 7 deletions src/applications/mhv-medical-records/api/MrApi.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import environment from '@department-of-veterans-affairs/platform-utilities/environment';
import { apiRequest } from '@department-of-veterans-affairs/platform-utilities/exports';
import { formatISO } from 'date-fns';
import { findMatchingPhrAndCvixStudies } from '../util/radiologyUtil';
import edipiNotFound from '../util/edipiNotFound';

Expand Down Expand Up @@ -219,14 +218,10 @@ export const getMedications = async () => {
* Get a patient's appointments
* @returns list of patient's appointments
*/
export const getAppointments = async () => {
const beginningOfTime = new Date(0);
const farFutureDate = new Date(2100, 0, 1); // January 1, 2100
const startDate = formatISO(beginningOfTime);
const endDate = formatISO(farFutureDate);
export const getAppointments = async (fromDate, toDate) => {
const statusParams =
'&statuses[]=booked&statuses[]=arrived&statuses[]=fulfilled&statuses[]=cancelled';
const params = `_include=facilities,clinics&start=${startDate}&end=${endDate}${statusParams}`;
const params = `_include=facilities,clinics&start=${fromDate}&end=${toDate}${statusParams}`;

return apiRequest(`${environment.API_URL}/vaos/v2/appointments?${params}`, {
headers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ const DownloadFileType = props => {

const [downloadStarted, setDownloadStarted] = useState(false);

const { fromDate, toDate, option: dateFilterOption } = dateFilter;

useEffect(
() => {
focusElement(document.querySelector('h1'));
Expand All @@ -85,26 +87,26 @@ const DownloadFileType = props => {

useEffect(
() => {
if (!dateFilter) {
if (!dateFilterOption) {
history.push('/download/date-range');
} else if (!recordFilter) {
history.push('/download/record-type');
}
},
[dateFilter, history, recordFilter],
[dateFilterOption, history, recordFilter],
);

const filterByDate = useCallback(
recDate => {
if (dateFilter.option === 'any') {
if (dateFilterOption === 'any') {
return true;
}
return (
isBefore(new Date(dateFilter.fromDate), new Date(recDate)) &&
isAfter(new Date(dateFilter.toDate), new Date(recDate))
isBefore(new Date(fromDate), new Date(recDate)) &&
isAfter(new Date(toDate), new Date(recDate))
);
},
[dateFilter],
[dateFilterOption, fromDate, toDate],
);

/**
Expand Down Expand Up @@ -191,10 +193,10 @@ const DownloadFileType = props => {
};

if (!isDataFetched) {
dispatch(getBlueButtonReportData(options));
dispatch(getBlueButtonReportData(options, dateFilter));
}
},
[isDataFetched, recordFilter, dispatch],
[isDataFetched, recordFilter, dispatch, dateFilter],
);

const recordData = useMemo(
Expand Down Expand Up @@ -306,13 +308,9 @@ const DownloadFileType = props => {
const pdfName = `VA-Blue-Button-report-${getNameDateAndTime(user)}`;
const pdfData = {
fromDate:
dateFilter?.fromDate && dateFilter.fromDate !== 'any'
? formatDateLong(dateFilter.fromDate)
: 'Any',
fromDate && fromDate !== 'any' ? formatDateLong(fromDate) : 'Any',
toDate:
dateFilter?.fromDate && dateFilter.fromDate !== 'any'
? formatDateLong(dateFilter.toDate)
: 'any',
fromDate && fromDate !== 'any' ? formatDateLong(toDate) : 'any',
recordSets: generateBlueButtonData(recordData, recordFilter),
...scaffold,
name,
Expand All @@ -337,8 +335,8 @@ const DownloadFileType = props => {
}
},
[
dateFilter.fromDate,
dateFilter.toDate,
fromDate,
toDate,
dispatch,
dob,
isDataFetched,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const DownloadRecordType = () => {
const [milServCheck, setMilServCheck] = useState(false);

const dateFilter = useSelector(state => state.mr.downloads?.dateFilter);
const { fromDate, toDate, option: dateFilterOption } = dateFilter;

const [selectedRecords, setSelectedRecords] = useState([]);
const [selectionError, setSelectionError] = useState(null);
Expand Down Expand Up @@ -98,24 +99,24 @@ const DownloadRecordType = () => {

useEffect(
() => {
if (!dateFilter) {
if (!dateFilterOption) {
history.push('/download/date-range');
}
},
[dateFilter, history],
[dateFilterOption, history],
);

const selectedDateRange = useMemo(
() => {
if (dateFilter?.option === 'any') {
if (dateFilterOption === 'any') {
return 'Any';
}
if (dateFilter?.option === 'custom') {
if (dateFilterOption === 'custom') {
return 'Custom';
}
return `Last ${dateFilter?.option} months`;
return `Last ${dateFilterOption} months`;
},
[dateFilter],
[dateFilterOption],
);

return (
Expand All @@ -136,9 +137,9 @@ const DownloadRecordType = () => {
<div className="vads-u-border-top--1px vads-u-border-bottom--1px vads-u-border-color--gray-light">
<p>
Date range: <strong>{selectedDateRange}</strong>{' '}
{dateFilter && dateFilter?.option !== 'any'
? `(${format(new Date(dateFilter?.fromDate), 'PPP')} to ${format(
new Date(dateFilter?.toDate),
{dateFilterOption && dateFilterOption !== 'any'
? `(${format(new Date(fromDate), 'PPP')} to ${format(
new Date(toDate),
'PPP',
)})`
: ''}
Expand Down
6 changes: 6 additions & 0 deletions src/applications/mhv-medical-records/reducers/blueButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,12 @@ export const blueButtonReducer = (state = initialState, action) => {
failedDomains: [],
};
}
case Actions.BlueButtonReport.CLEAR_APPOINTMENTS: {
return {
...state,
appointmentsList: undefined,
};
}
default:
return state;
}
Expand Down
1 change: 1 addition & 0 deletions src/applications/mhv-medical-records/reducers/downloads.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const initialState = {
* @type {Boolean}
*/
error: false,
dateFilter: {},
};

export const downloadsReducer = (state = initialState, action) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { expect } from 'chai';
import { mockApiRequest } from '@department-of-veterans-affairs/platform-testing/helpers';
import sinon from 'sinon';
import { formatISO, subYears } from 'date-fns';
import {
getBlueButtonReportData,
clearFailedList,
} from '../../actions/blueButtonReport';
import { Actions } from '../../util/actionTypes';
import allergies from '../fixtures/allergies.json';
import * as MrApi from '../../api/MrApi';

describe('Blue Button Actions', () => {
describe('getBlueButtonReportData', () => {
Expand Down Expand Up @@ -159,6 +161,75 @@ describe('Blue Button Actions', () => {
);
});
});

it('should fetch appointments with the correct date range when dateFilter.option is "any"', async () => {
const mockData = { mockData: 'appointmentsMockData' };
const getAppointmentsStub = sinon
.stub(MrApi, 'getAppointments')
.resolves(mockData);

const dispatch = sinon.spy();
const action = getBlueButtonReportData(
{ appointments: true },
{ option: 'any' },
);
await action(dispatch);

// Verify that getAppointments was called once
expect(getAppointmentsStub.calledOnce).to.be.true;

// The 'any' option should calculate fromDate as 2 years ago and toDate as far future
const currentDate = new Date();
const dateTwoYearsAgo = subYears(currentDate, 2);
const farFutureDate = new Date(2099, 0, 1);

const expectedFromDate = formatISO(dateTwoYearsAgo);
const expectedToDate = formatISO(farFutureDate);

const [fromDateArg, toDateArg] = getAppointmentsStub.firstCall.args;
expect(fromDateArg).to.equal(expectedFromDate);
expect(toDateArg).to.equal(expectedToDate);

// Verify dispatch
expect(dispatch.calledOnce).to.be.true;
const dispatchedAction = dispatch.firstCall.args[0];
expect(dispatchedAction.type).to.equal(Actions.BlueButtonReport.GET);
expect(dispatchedAction.appointmentsResponse).to.deep.equal(mockData);

getAppointmentsStub.restore();
});

it('should fetch appointments with the provided custom dateFilter range', async () => {
const mockData = { mockData: 'appointmentsMockData' };
const getAppointmentsStub = sinon
.stub(MrApi, 'getAppointments')
.resolves(mockData);

const fromDateCustom = '2021-01-01';
const toDateCustom = '2021-12-31';
const dispatch = sinon.spy();
const action = getBlueButtonReportData(
{ appointments: true },
{ fromDate: fromDateCustom, toDate: toDateCustom },
);
await action(dispatch);

// Verify that getAppointments was called once
expect(getAppointmentsStub.calledOnce).to.be.true;

// Check arguments passed to getAppointments
const [fromDateArg, toDateArg] = getAppointmentsStub.firstCall.args;
expect(fromDateArg).to.equal(formatISO(new Date(fromDateCustom)));
expect(toDateArg).to.equal(formatISO(new Date(toDateCustom)));

// Verify dispatch
expect(dispatch.calledOnce).to.be.true;
const dispatchedAction = dispatch.firstCall.args[0];
expect(dispatchedAction.type).to.equal(Actions.BlueButtonReport.GET);
expect(dispatchedAction.appointmentsResponse).to.deep.equal(mockData);

getAppointmentsStub.restore();
});
});

it('should not dispatch any actions if no options are enabled', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe('Download Actions', () => {
const fromDate = 'from';
const toDate = 'to';
updateReportDateRange(option, fromDate, toDate)(dispatch);
expect(dispatch.calledOnce).to.be.true;
expect(dispatch.calledTwice).to.be.true;
expect(dispatch.firstCall.args[0].type).to.equal(
Actions.Downloads.SET_DATE_FILTER,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from 'fs';
import { expect } from 'chai';
import { mockApiRequest } from '@department-of-veterans-affairs/platform-testing/helpers';
import Sinon from 'sinon';
import environment from '@department-of-veterans-affairs/platform-utilities/environment';
import labsAndTests from '../fixtures/labsAndTests.json';
import pathology from '../fixtures/pathology.json';
import notes from '../fixtures/notes.json';
Expand Down Expand Up @@ -261,10 +262,39 @@ describe('Get appointments api call', () => {
const mockData = appointments;
mockApiRequest(mockData);

return getAppointments().then(res => {
const fromDate = '2020-01-01T00:00:00Z';
const toDate = '2020-12-31T23:59:59Z';

return getAppointments(fromDate, toDate).then(res => {
expect(res.data.length).to.equal(2);
});
});

it('should call the correct endpoint with provided from and to dates', async () => {
const fetchStub = Sinon.stub(global, 'fetch');
const fromDate = '2021-01-01T00:00:00Z';
const toDate = '2021-12-31T23:59:59Z';

const mockResponse = new Response(JSON.stringify({ data: [{}, {}] }), {
status: 200,
headers: { 'Content-type': 'application/json' },
});

fetchStub.resolves(mockResponse);

const result = await getAppointments(fromDate, toDate);
expect(fetchStub.calledOnce).to.be.true;

const statusParams =
'&statuses[]=booked&statuses[]=arrived&statuses[]=fulfilled&statuses[]=cancelled';
const expectedUrl = `${
environment.API_URL
}/vaos/v2/appointments?_include=facilities,clinics&start=${fromDate}&end=${toDate}${statusParams}`;
expect(fetchStub.firstCall.args[0]).to.equal(expectedUrl);
expect(result.data.length).to.equal(2);

fetchStub.restore();
});
});

describe('Get demographic info api call', () => {
Expand Down
Loading

0 comments on commit 5b12bcc

Please sign in to comment.