Skip to content

Commit

Permalink
Merge pull request #1339 from tidepool-org/WEB-2763-pendo-patient-count
Browse files Browse the repository at this point in the history
[WEB-2763] Add clinic total patient count to Pendo
  • Loading branch information
clintonium-119 authored Feb 28, 2024
2 parents 05dd56d + e2e5d72 commit ce6358e
Show file tree
Hide file tree
Showing 33 changed files with 814 additions and 86 deletions.
4 changes: 2 additions & 2 deletions app/components/clinic/WorkspaceSwitcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import Popover from '../elements/Popover';
import { colors } from '../../themes/baseTheme';

export const WorkspaceSwitcher = props => {
const { t, trackMetric } = props;
const { t, api, trackMetric } = props;
const dispatch = useDispatch();
const loggedInUserId = useSelector((state) => state.blip.loggedInUserId);
const allUsersMap = useSelector((state) => state.blip.allUsersMap);
Expand Down Expand Up @@ -77,7 +77,7 @@ export const WorkspaceSwitcher = props => {

const handleSelect = option => {
trackMetric(...option.metric);
dispatch(actions.sync.selectClinic(option.id));
dispatch(actions.async.selectClinic(api, option.id));
dispatch(push(option.id ? '/clinic-workspace' : '/patients', { selectedClinicId: option.id }));
popupState.close();
};
Expand Down
2 changes: 1 addition & 1 deletion app/components/navbar/NavigationMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const NavigationMenu = props => {
}, [pathname]);

function handleSelectWorkspace(clinicId) {
dispatch(actions.sync.selectClinic(clinicId));
dispatch(actions.async.selectClinic(api, clinicId));
dispatch(push(clinicId ? '/clinic-workspace' : '/patients', { selectedClinicId: clinicId }));
}

Expand Down
8 changes: 8 additions & 0 deletions app/core/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,14 @@ api.clinics.getPatientsForTideDashboard = function(clinicId, options, cb) {
return tidepool.getPatientsForTideDashboard(clinicId, options, cb);
};

api.clinics.getClinicPatientCount = function(clinicId, cb) {
return tidepool.getClinicPatientCount(clinicId, cb);
};

api.clinics.getClinicPatientCountSettings = function(clinicId, cb) {
return tidepool.getClinicPatientCountSettings(clinicId, cb);
};

// ----- Errors -----

api.errors = {};
Expand Down
4 changes: 2 additions & 2 deletions app/pages/clinicworkspace/ClinicPatients.js
Original file line number Diff line number Diff line change
Expand Up @@ -2933,7 +2933,7 @@ export const ClinicPatients = (props) => {
const tableStyle = useMemo(() => ({ fontSize: showSummaryData ? '12px' : '14px' }), [showSummaryData]);

const renderPeopleTable = useCallback(() => {
const pageCount = Math.ceil(clinic?.patientCount / patientFetchOptions.limit);
const pageCount = Math.ceil(clinic?.fetchedPatientCount / patientFetchOptions.limit);
const page = Math.ceil(patientFetchOptions.offset / patientFetchOptions.limit) + 1;
const sort = patientFetchOptions.sort || defaultPatientFetchOptions.sort;
return (
Expand Down Expand Up @@ -2968,7 +2968,7 @@ export const ClinicPatients = (props) => {
</Box>
);
}, [
clinic?.patientCount,
clinic?.fetchedPatientCount,
columns,
data,
defaultPatientFetchOptions.sort,
Expand Down
2 changes: 1 addition & 1 deletion app/pages/clinicworkspace/clinicworkspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const ClinicWorkspace = (props) => {
dispatch(actions.sync.clearPatientInView());

if (props.location?.state?.selectedClinicId && props.location?.state?.selectedClinicId !== selectedClinicId) {
dispatch(actions.sync.selectClinic(props.location?.state?.selectedClinicId));
dispatch(actions.async.selectClinic(api, props.location?.state?.selectedClinicId));
}
}, [props.location?.state?.selectedClinicId]);

Expand Down
4 changes: 2 additions & 2 deletions app/pages/workspaces/workspaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,13 @@ export const Workspaces = (props) => {
: ['Clinic - Workspaces - Go to private workspace'];

trackMetric(...metric);
dispatch(actions.sync.selectClinic(workspace?.id || null));
dispatch(actions.async.selectClinic(api, workspace?.id || null));
dispatch(push(workspace?.id ? '/clinic-workspace' : '/patients', { selectedClinicId: workspace.id }));
}

function handleCreateNewClinic(source) {
trackMetric('Clinic - Workspaces - Create new clinic', { source });
dispatch(actions.sync.selectClinic(null));
dispatch(actions.async.selectClinic(api, null));
dispatch(push('/clinic-details/new', { selectedClinicId: null, referrer: location.pathname }));
}

Expand Down
65 changes: 61 additions & 4 deletions app/redux/actions/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ export function login(api, credentials, options, postLoginAction) {
// internal route, such as on page refresh, dispatch the selectClinic action so
// that middlewares (currently Pendo and LaunchDarkly) can react to it.
if (userHasClinicProfile && selectedClinicId && hasDest) {
dispatch(sync.selectClinic(selectedClinicId));
dispatch(selectClinic(api, selectedClinicId));
}

// If we have an empty clinic profile, go to clinic details, otherwise workspaces
Expand All @@ -306,12 +306,12 @@ export function login(api, credentials, options, postLoginAction) {
if (values.clinics.length === 1) {
selectedClinicId = values.clinics[0]?.clinic?.id;
}
dispatch(sync.selectClinic(selectedClinicId));
dispatch(selectClinic(api, selectedClinicId));
setRedirectRoute(routes.clinicWorkspace, selectedClinicId);
} else {
// If we have an empty clinic object, go to clinic details, otherwise workspaces
if (hasLegacyClinicRole && clinicMigration) {
dispatch(sync.selectClinic(clinicMigration.clinic?.id));
dispatch(selectClinic(api, clinicMigration.clinic?.id));
setRedirectRoute(`${routes.clinicDetails}/migrate`, values.clinics[0]?.clinic?.id);
} else {
setRedirectRoute(routes.workspaces);
Expand Down Expand Up @@ -1812,7 +1812,7 @@ export function createClinic(api, clinic, clinicianId) {
)
);
} else {
dispatch(sync.selectClinic(clinic.id));
dispatch(selectClinic(api, clinic.id));
dispatch(sync.createClinicSuccess(clinic));
dispatch(getClinicsForClinician(api, clinicianId, { limit: 1000, offset: 0 }));
}
Expand Down Expand Up @@ -2854,3 +2854,60 @@ export function deleteClinicPatientTag(api, clinicId, patientTagId) {
});
};
}

/**
* Select Clinic Action Creator
*
* Immediately sets or unsets the selected clinic to state,
* then fetches additional clinic metadata asynchronously.
*
* @param {Object} api - an instance of the API wrapper
* @param {String | null} clinicId - Id of the clinic, or null do unset
*/
export function selectClinic(api, clinicId) {
return (dispatch, getState) => {
dispatch(sync.selectClinicSuccess(clinicId));

const { blip: { clinics = {} } } = getState();
const clinic = clinics[clinicId];

if (clinic) {
const fetchers = {};

if (_.isNil(clinics[clinicId].patientCount)) {
fetchers.clinicPatientCount = api.clinics.getClinicPatientCount.bind(api, clinicId);
dispatch(sync.fetchClinicPatientCountRequest());
}

if (_.isNil(clinics[clinicId].patientCountSettings)) {
fetchers.clinicPatientCountSettings = api.clinics.getClinicPatientCountSettings.bind(api, clinicId);
dispatch(sync.fetchClinicPatientCountSettingsRequest());
}

async.parallel(async.reflectAll(fetchers), (err, results) => {
const errors = _.mapValues(results, ({error}) => error);
const values = _.mapValues(results, ({value}) => value);

if (errors?.clinicPatientCount) {
dispatch(sync.fetchClinicPatientCountFailure(
createActionError(ErrorMessages.ERR_FETCHING_CLINIC_PATIENT_COUNT, errors.clinicPatientCount), errors.clinicPatientCount
));
}

if (errors?.clinicPatientCountSettings) {
dispatch(sync.fetchClinicPatientCountSettingsFailure(
createActionError(ErrorMessages.ERR_FETCHING_CLINIC_PATIENT_COUNT_SETTINGS, errors.clinicPatientCountSettings), errors.clinicPatientCountSettings
));
}

if (values.clinicPatientCount) {
dispatch(sync.fetchClinicPatientCountSuccess(clinicId, values.clinicPatientCount));
}

if (values.clinicPatientCountSettings) {
dispatch(sync.fetchClinicPatientCountSettingsSuccess(clinicId, values.clinicPatientCountSettings));
}
});
}
};
}
56 changes: 54 additions & 2 deletions app/redux/actions/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -1989,9 +1989,9 @@ export function getClinicsForClinicianFailure(error, apiError) {
};
}

export function selectClinic(clinicId) {
export function selectClinicSuccess(clinicId) {
return {
type: ActionTypes.SELECT_CLINIC,
type: ActionTypes.SELECT_CLINIC_SUCCESS,
payload: {
clinicId
},
Expand Down Expand Up @@ -2308,3 +2308,55 @@ export function setSSOEnabledDisplay(value) {
payload: { value },
};
}

export function fetchClinicPatientCountRequest() {
return {
type: ActionTypes.FETCH_CLINIC_PATIENT_COUNT_REQUEST,
};
}

export function fetchClinicPatientCountSuccess(clinicId, results) {
return {
type: ActionTypes.FETCH_CLINIC_PATIENT_COUNT_SUCCESS,
payload: {
clinicId: clinicId,
patientCount: results.patientCount,
},
};
}

export function fetchClinicPatientCountFailure(error, apiError) {
return {
type: ActionTypes.FETCH_CLINIC_PATIENT_COUNT_FAILURE,
error,
meta: {
apiError: apiError || null,
},
};
}

export function fetchClinicPatientCountSettingsRequest() {
return {
type: ActionTypes.FETCH_CLINIC_PATIENT_COUNT_SETTINGS_REQUEST,
};
}

export function fetchClinicPatientCountSettingsSuccess(clinicId, patientCountSettings) {
return {
type: ActionTypes.FETCH_CLINIC_PATIENT_COUNT_SETTINGS_SUCCESS,
payload: {
clinicId: clinicId,
patientCountSettings: patientCountSettings,
},
};
}

export function fetchClinicPatientCountSettingsFailure(error, apiError) {
return {
type: ActionTypes.FETCH_CLINIC_PATIENT_COUNT_SETTINGS_FAILURE,
error,
meta: {
apiError: apiError || null,
},
};
}
10 changes: 9 additions & 1 deletion app/redux/constants/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const CLOSE_MESSAGE_THREAD = 'CLOSE_MESSAGE_THREAD';
export const ADD_PATIENT_NOTE = 'ADD_PATIENT_NOTE';
export const UPDATE_PATIENT_NOTE = 'UPDATE_PATIENT_NOTE';

export const SELECT_CLINIC = 'SELECT_CLINIC';
export const SELECT_CLINIC_SUCCESS = 'SELECT_CLINIC_SUCCESS';

/*
* Asyncronous action types
Expand Down Expand Up @@ -508,3 +508,11 @@ export const FETCH_TIDE_DASHBOARD_PATIENTS_FAILURE = 'FETCH_TIDE_DASHBOARD_PATIE
export const CLEAR_TIDE_DASHBOARD_PATIENTS = 'CLEAR_TIDE_DASHBOARD_PATIENTS';

export const SET_SSO_ENABLED_DISPLAY = 'SET_SSO_ENABLED_DISPLAY';

export const FETCH_CLINIC_PATIENT_COUNT_REQUEST = 'FETCH_CLINIC_PATIENT_COUNT_REQUEST';
export const FETCH_CLINIC_PATIENT_COUNT_SUCCESS = 'FETCH_CLINIC_PATIENT_COUNT_SUCCESS';
export const FETCH_CLINIC_PATIENT_COUNT_FAILURE = 'FETCH_CLINIC_PATIENT_COUNT_FAILURE';

export const FETCH_CLINIC_PATIENT_COUNT_SETTINGS_REQUEST = 'FETCH_CLINIC_PATIENT_COUNT_SETTINGS_REQUEST';
export const FETCH_CLINIC_PATIENT_COUNT_SETTINGS_SUCCESS = 'FETCH_CLINIC_PATIENT_COUNT_SETTINGS_SUCCESS';
export const FETCH_CLINIC_PATIENT_COUNT_SETTINGS_FAILURE = 'FETCH_CLINIC_PATIENT_COUNT_SETTINGS_FAILURE';
10 changes: 10 additions & 0 deletions app/redux/constants/actionWorkingMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,16 @@ export default (type) => {
case types.FETCH_TIDE_DASHBOARD_PATIENTS_FAILURE:
return 'fetchingTideDashboardPatients';

case types.FETCH_CLINIC_PATIENT_COUNT_REQUEST:
case types.FETCH_CLINIC_PATIENT_COUNT_SUCCESS:
case types.FETCH_CLINIC_PATIENT_COUNT_FAILURE:
return 'fetchingClinicPatientCount';

case types.FETCH_CLINIC_PATIENT_COUNT_SETTINGS_REQUEST:
case types.FETCH_CLINIC_PATIENT_COUNT_SETTINGS_SUCCESS:
case types.FETCH_CLINIC_PATIENT_COUNT_SETTINGS_FAILURE:
return 'fetchingClinicPatientCountSettings';

default:
return null;
}
Expand Down
2 changes: 2 additions & 0 deletions app/redux/constants/errorMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ export const ERR_UPDATING_CLINIC_PATIENT_TAG_DUPLICATE = t('Sorry, you already h
export const ERR_DELETING_CLINIC_PATIENT_TAG = t('Something went wrong while deleting the patient tag.');
export const ERR_FETCHING_INFO = t('Something went wrong while fetching server configuration.');
export const ERR_FETCHING_TIDE_DASHBOARD_PATIENTS = t('Something went wrong while fetching patients for the dashboard.');
export const ERR_FETCHING_CLINIC_PATIENT_COUNT = t('Something went wrong while fetching the clinic patient count.');
export const ERR_FETCHING_CLINIC_PATIENT_COUNT_SETTINGS = t('Something went wrong while fetching the clinic patient count settings.');

export const ERR_BIRTHDAY_INVALID = t('Birthday is invalid.');
export const ERR_BIRTHDAY_MISSING = t('Birthday is required.');
Expand Down
2 changes: 2 additions & 0 deletions app/redux/reducers/initialState.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ const initialState = {
deletingClinicPatientTag: Object.assign({}, working),
fetchingInfo: Object.assign({}, working),
fetchingTideDashboardPatients: Object.assign({}, working),
fetchingClinicPatientCount: Object.assign({}, working),
fetchingClinicPatientCountSettings: Object.assign({}, working),
},
notification: null,
timePrefs: {
Expand Down
30 changes: 25 additions & 5 deletions app/redux/reducers/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ export const clinics = (state = initialState.clinics, action) => {
return newSet;
}, {});
return update(state, {
[clinicId]: { $set: { ...state[clinicId], patients: newPatientSet, patientCount: count, lastPatientFetchTime: moment.utc().valueOf() } },
[clinicId]: { $set: { ...state[clinicId], patients: newPatientSet, fetchedPatientCount: count, lastPatientFetchTime: moment.utc().valueOf() } },
});
}
case types.FETCH_PATIENTS_FOR_CLINIC_FAILURE: {
Expand All @@ -710,7 +710,7 @@ export const clinics = (state = initialState.clinics, action) => {
} = action;
return update(state, {
[clinicId]: {
$set: { ...state[clinicId], patients: {}, patientCount: 0 },
$set: { ...state[clinicId], patients: {}, fetchedPatientCount: 0 },
},
});
}
Expand Down Expand Up @@ -810,15 +810,20 @@ export const clinics = (state = initialState.clinics, action) => {
const patient = _.get(action.payload, 'patient');
const patientId = _.get(action.payload, 'patientId');
const clinicId = _.get(action.payload, 'clinicId');
let fetchedPatientCount = state[clinicId].fetchedPatientCount;
let patientCount = state[clinicId].patientCount;

// Retain existing sortIndex, or, in the case of a new custodial patient, set to -1 to show at top of
// list for easy visibility of the newly created patient.
const existingSortIndex = state[clinicId]?.patients?.[patientId]?.sortIndex || -1;

if (action.type === types.CREATE_CLINIC_CUSTODIAL_ACCOUNT_SUCCESS) patientCount++;
if (action.type === types.CREATE_CLINIC_CUSTODIAL_ACCOUNT_SUCCESS) {
fetchedPatientCount++;
patientCount++;
}

return update(state, {
[clinicId]: { patients: { [patientId]: { $set: { ...patient, sortIndex: existingSortIndex } } }, patientCount: { $set: patientCount } },
[clinicId]: { patients: { [patientId]: { $set: { ...patient, sortIndex: existingSortIndex } } }, fetchedPatientCount: { $set: fetchedPatientCount }, patientCount: { $set: patientCount } },
});
}
case types.DELETE_CLINICIAN_FROM_CLINIC_SUCCESS: {
Expand All @@ -833,6 +838,7 @@ export const clinics = (state = initialState.clinics, action) => {
let patientId = _.get(action.payload, 'patientId');
let newState = _.cloneDeep(state);
delete newState[clinicId]?.patients?.[patientId];
if (newState[clinicId]?.fetchedPatientCount) newState[clinicId].fetchedPatientCount--;
if (newState[clinicId]?.patientCount) newState[clinicId].patientCount--;
return newState;
}
Expand Down Expand Up @@ -968,6 +974,20 @@ export const clinics = (state = initialState.clinics, action) => {
[clinicId]: { mrnSettings: { $set: settings } },
});
}
case types.FETCH_CLINIC_PATIENT_COUNT_SUCCESS: {
const { clinicId, patientCount } = action.payload;

return update(state, {
[clinicId]: { patientCount: { $set: patientCount } },
});
}
case types.FETCH_CLINIC_PATIENT_COUNT_SETTINGS_SUCCESS: {
const { clinicId, patientCountSettings } = action.payload;

return update(state, {
[clinicId]: { patientCountSettings: { $set: patientCountSettings } },
});
}
case types.LOGOUT_REQUEST:
return initialState.clinics;
default:
Expand All @@ -977,7 +997,7 @@ export const clinics = (state = initialState.clinics, action) => {

export const selectedClinicId = (state = initialState.selectedClinicId, action) => {
switch(action.type) {
case types.SELECT_CLINIC:
case types.SELECT_CLINIC_SUCCESS:
return _.get(action.payload, 'clinicId', null);
case types.LOGOUT_REQUEST:
return null;
Expand Down
Loading

0 comments on commit ce6358e

Please sign in to comment.