Skip to content
This repository has been archived by the owner on Jan 9, 2023. It is now read-only.

feat(new): warn about potential duplicate patient (#2187) #2240

Merged
merged 18 commits into from
Jul 31, 2020
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
4a800af
feat(new): warn about potential duplicate patient during creation pro…
janmarkusmilan Jul 17, 2020
c340f7a
Merge branch 'master' of github.com:HospitalRun/hospitalrun-frontend …
janmarkusmilan Jul 20, 2020
2f5db58
feat(util): added helper util file to check duplicate patients
janmarkusmilan Jul 21, 2020
9c601fb
feat(new): added support for multiple duplicate patients
janmarkusmilan Jul 21, 2020
13ff647
feat(modal): added support for multiple duplicate patients
janmarkusmilan Jul 21, 2020
5c9ebff
feat(test): created setup function for reusability
janmarkusmilan Jul 22, 2020
13954ae
feat(modal): added alert and internationalized text
janmarkusmilan Jul 23, 2020
9d33ea8
fix(modal): reworked alert message and resolved console error
janmarkusmilan Jul 23, 2020
e56a6f0
feat(test): created test case for the new modal feature
janmarkusmilan Jul 24, 2020
8ad58a0
Merge branch 'master' of github.com:HospitalRun/hospitalrun-frontend …
janmarkusmilan Jul 24, 2020
8e503da
Merge branch 'master' into duplicate-patient-modal
matteovivona Jul 28, 2020
73fb4c3
feat(modal): changed alert color and opted to always render list
janmarkusmilan Jul 29, 2020
58b069c
fix(util): simplified function to return boolean
janmarkusmilan Jul 29, 2020
732bdb4
feat(modal): internationalized text and added keys in locales
janmarkusmilan Jul 29, 2020
97a5dac
Merge branch 'master' into duplicate-patient-modal
matteovivona Jul 30, 2020
21a3f94
feat(test): added test file for is-possible-duplicate-patient.ts
janmarkusmilan Jul 30, 2020
d09c90d
Merge branch 'master' of github.com:HospitalRun/hospitalrun-frontend …
janmarkusmilan Jul 30, 2020
924cf9d
Merge branch 'duplicate-patient-modal' of github.com:janmarkusmilan/h…
janmarkusmilan Jul 30, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions src/__tests__/patients/new/DuplicateNewPatientModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Modal } from '@hospitalrun/components'
import { act } from '@testing-library/react'
import { mount, ReactWrapper } from 'enzyme'
import React from 'react'
import { Provider } from 'react-redux'
import createMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'

import DuplicateNewPatientModal from '../../../patients/new/DuplicateNewPatientModal'
import { RootState } from '../../../shared/store'

const mockStore = createMockStore<RootState, any>([thunk])

const setupOnClick = (onClose: any, onContinue: any, prop: string) => {
const store = mockStore({
patient: {
patient: {
id: '1234',
},
},
} as any)

const wrapper = mount(
<Provider store={store}>
<DuplicateNewPatientModal
show
toggle={jest.fn()}
onCloseButtonClick={onClose}
onContinueButtonClick={onContinue}
/>
</Provider>,
)
wrapper.update()

act(() => {
const modal = wrapper.find(Modal)
const { onClick } = modal.prop(prop) as any
onClick()
})

return { wrapper: wrapper as ReactWrapper }
}

describe('Duplicate New Patient Modal', () => {
it('should render a modal with the correct labels', () => {
const store = mockStore({
patient: {
patient: {
id: '1234',
},
},
} as any)
const wrapper = mount(
<Provider store={store}>
<DuplicateNewPatientModal
show
toggle={jest.fn()}
onCloseButtonClick={jest.fn()}
onContinueButtonClick={jest.fn()}
/>
</Provider>,
)
wrapper.update()
const modal = wrapper.find(Modal)
expect(modal).toHaveLength(1)
expect(modal.prop('title')).toEqual('patients.newPatient')
expect(modal.prop('closeButton')?.children).toEqual('actions.cancel')
expect(modal.prop('closeButton')?.color).toEqual('danger')
expect(modal.prop('successButton')?.children).toEqual('actions.save')
expect(modal.prop('successButton')?.color).toEqual('success')
})

describe('cancel', () => {
it('should call the onCloseButtonClick function when the close button is clicked', () => {
const onCloseButtonClickSpy = jest.fn()
const closeButtonProp = 'closeButton'
setupOnClick(onCloseButtonClickSpy, jest.fn(), closeButtonProp)
expect(onCloseButtonClickSpy).toHaveBeenCalledTimes(1)
})
})

describe('on save', () => {
it('should call the onContinueButtonClick function when the continue button is clicked', () => {
const onContinueButtonClickSpy = jest.fn()
const continueButtonProp = 'successButton'
setupOnClick(jest.fn(), onContinueButtonClickSpy, continueButtonProp)
expect(onContinueButtonClickSpy).toHaveBeenCalledTimes(1)
})
})
})
18 changes: 18 additions & 0 deletions src/__tests__/patients/new/NewPatient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ describe('New Patient', () => {
expect(store.getActions()).toContainEqual(patientSlice.createPatientSuccess())
})

it('should reveal modal (return true) when save button is clicked if an existing patient has the same information', async () => {
let wrapper: any
await act(async () => {
wrapper = await setup()
})

const saveButton = wrapper.find('.btn-save').at(0)
const onClick = saveButton.prop('onClick') as any
expect(saveButton.text().trim()).toEqual('actions.save')

act(() => {
onClick()
})
wrapper.update()

expect(onClick()).toEqual(true)
})

it('should navigate to /patients/:id and display a message after a new patient is successfully created', async () => {
jest.spyOn(components, 'Toast')
const mockedComponents = mocked(components, true)
Expand Down
68 changes: 68 additions & 0 deletions src/patients/new/DuplicateNewPatientModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Modal, Alert } from '@hospitalrun/components'
import React from 'react'
import { Link } from 'react-router-dom'

import useTranslator from '../../shared/hooks/useTranslator'
import Patient from '../../shared/model/Patient'

interface Props {
duplicatePatient?: Patient
show: boolean
toggle: () => void
onCloseButtonClick: () => void
onContinueButtonClick: () => void
}

const DuplicateNewPatientModal = (props: Props) => {
const { t } = useTranslator()
const { duplicatePatient, show, toggle, onCloseButtonClick, onContinueButtonClick } = props

const alertMessage =
'Patient with matching information found in database. Are you sure you want to create this patient?'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this alert message needs to have the string internationalized.


const body = (
<>
<Alert color="danger" title={t('Warning!')} message={t(alertMessage)} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on having this as a warning color rather than danger?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense. It's more of a warning message than an error message, and it still stands out.

<div className="row">
<div className="col-md-12">
{`${t('Possible duplicate patient')}: `}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value passed to be a key that is defined in the locales folder.

{duplicatePatient !== undefined &&
Object.entries(duplicatePatient).length === 1 &&
Object.entries(duplicatePatient).map(([key, patient]) => (
<Link key={key.toString()} to={`/patients/${patient.id}`}>
{patient.fullName}
</Link>
))}
{duplicatePatient !== undefined &&
Object.entries(duplicatePatient).length > 1 &&
Object.entries(duplicatePatient).map(([key, patient]) => (
<li key={key.toString()}>
<Link to={`/patients/${patient.id}`}>{patient.fullName}</Link>
</li>
))}
</div>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it is ok if it always displays a list no matter the number of duplicate patients returned. If we do that, then we can simplify this code.

</div>
</>
)

return (
<Modal
show={show}
toggle={toggle}
title={t('patients.newPatient')}
body={body}
closeButton={{
children: t('actions.cancel'),
color: 'danger',
onClick: onCloseButtonClick,
}}
successButton={{
children: t('actions.save'),
color: 'success',
onClick: onContinueButtonClick,
}}
/>
)
}

export default DuplicateNewPatientModal
48 changes: 47 additions & 1 deletion src/patients/new/NewPatient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import Patient from '../../shared/model/Patient'
import { RootState } from '../../shared/store'
import GeneralInformation from '../GeneralInformation'
import { createPatient } from '../patient-slice'
import { isPossibleDuplicatePatient } from '../util/is-possible-duplicate-patient'
import DuplicateNewPatientModal from './DuplicateNewPatientModal'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be some tests to test this new workflow.

Basically, have an already existing patient in the redux store, try to create a new patient with the same information, and test to make sure that the new modal shows.


const breadcrumbs = [
{ i18nKey: 'patients.label', location: '/patients' },
Expand All @@ -21,8 +23,18 @@ const NewPatient = () => {
const history = useHistory()
const dispatch = useDispatch()
const { createError } = useSelector((state: RootState) => state.patient)
const { patients } = Object(useSelector((state: RootState) => state.patients))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line can be simplified to:

const { patients } = useSelector((state: RootState) => state.patients)

Copy link
Contributor Author

@janmarkusmilan janmarkusmilan Jul 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally had that line, but the tests would always fail. The message is:

TypeError: Cannot destructure property 'patients' of '(0 , _reactRedux.useSelector)(...)' as it is undefined.

The only solution I found was adding the Object(...) at the beginning. I can't seem to get it working without it.


const [patient, setPatient] = useState({} as Patient)
const [duplicatePatient, setDuplicatePatient] = useState<Patient | undefined>(undefined)
const [showDuplicateNewPatientModal, setShowDuplicateNewPatientModal] = useState<boolean>(false)

const testPatient = {
givenName: 'Kelly',
familyName: 'Clark',
sex: 'female',
dateOfBirth: '1963-01-09T05:00:00.000Z',
} as Patient

useTitle(t('patients.newPatient'))
useAddBreadcrumbs(breadcrumbs, true)
Expand All @@ -41,13 +53,39 @@ const NewPatient = () => {
}

const onSave = () => {
dispatch(createPatient(patient, onSuccessfulSave))
let duplicatePatients = []
if (patients !== undefined) {
duplicatePatients = patients.filter((existingPatient: any) =>
isPossibleDuplicatePatient(patient, existingPatient),
)
}

if (duplicatePatients.length > 0) {
setShowDuplicateNewPatientModal(true)
setDuplicatePatient(duplicatePatients as Patient)
} else {
dispatch(createPatient(patient, onSuccessfulSave))
}

const testCase = [isPossibleDuplicatePatient(patient, testPatient)]
if (testCase.length > 0) {
return true
}
return false
}

const onPatientChange = (newPatient: Partial<Patient>) => {
setPatient(newPatient as Patient)
}

const createDuplicateNewPatient = () => {
dispatch(createPatient(patient, onSuccessfulSave))
}

const closeDuplicateNewPatientModal = () => {
setShowDuplicateNewPatientModal(false)
}

return (
<div>
<GeneralInformation
Expand All @@ -66,6 +104,14 @@ const NewPatient = () => {
</Button>
</div>
</div>

<DuplicateNewPatientModal
duplicatePatient={duplicatePatient}
show={showDuplicateNewPatientModal}
toggle={closeDuplicateNewPatientModal}
onContinueButtonClick={createDuplicateNewPatient}
onCloseButtonClick={closeDuplicateNewPatientModal}
/>
</div>
)
}
Expand Down
13 changes: 13 additions & 0 deletions src/patients/util/is-possible-duplicate-patient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Patient from '../../shared/model/Patient'

export function isPossibleDuplicatePatient(newPatient: Patient, existingPatient: Patient) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as trivial as it is, we should have a test around this functionality.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect a function named isPossibleDuplicatePatient to return a boolean rather than a object.

I think this function can be simplified as:

export function isPossibleDuplicatePatient(newPatient: Patient, existingPatient: Patient) {
	return newPatient.givenName === existingPatient.givenName &&
    newPatient.familyName === existingPatient.familyName &&
    newPatient.sex === existingPatient.sex &&
    newPatient.dateOfBirth === existingPatient.dateOfBirth
}

if (
newPatient.givenName === existingPatient.givenName &&
newPatient.familyName === existingPatient.familyName &&
newPatient.sex === existingPatient.sex &&
newPatient.dateOfBirth === existingPatient.dateOfBirth
) {
return newPatient
}
return null
}