diff --git a/packages/fhir-care-team/src/components/CreateEditCareTeam/Form.tsx b/packages/fhir-care-team/src/components/CreateEditCareTeam/Form.tsx index fe2048f2c..d17e38ffc 100644 --- a/packages/fhir-care-team/src/components/CreateEditCareTeam/Form.tsx +++ b/packages/fhir-care-team/src/components/CreateEditCareTeam/Form.tsx @@ -1,20 +1,22 @@ import React, { useState } from 'react'; import { useHistory } from 'react-router'; -import { Button, Col, Row, Form, Input, Radio, Select } from 'antd'; -import { BodyLayout } from '@opensrp/react-utils'; +import { Button, Col, Row, Form, Input, Radio } from 'antd'; +import { BodyLayout, PaginatedAsyncSelect, SelectOption } from '@opensrp/react-utils'; import { sendErrorNotification } from '@opensrp/notifications'; import { FormFields, - getOrgSelectOptions, - getPractitionerSelectOptions, - selectFilterFunction, + preloadExistingOptionsFactory, + processOrganizationOption, + processPractitionerOption, submitForm, } from './utils'; import { id, managingOrganizations, name, + organizationResourceType, practitionerParticipants, + practitionerResourceType, status, URL_CARE_TEAM, uuid, @@ -27,8 +29,6 @@ import { IPractitioner } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IPracti export interface CareTeamFormProps { initialValues: FormFields; fhirBaseURL: string; - practitioners: IPractitioner[]; - organizations: IOrganization[]; disabled?: string[]; } @@ -38,14 +38,15 @@ export interface CareTeamFormProps { * @param {object} props - component props */ const CareTeamForm: React.FC = (props: CareTeamFormProps) => { - const { fhirBaseURL, initialValues, practitioners, organizations, disabled } = props; + const { fhirBaseURL, initialValues, disabled } = props; const [isSubmitting, setIsSubmitting] = useState(false); const history = useHistory(); const { t } = useTranslation(); const [form] = Form.useForm(); - - const orgOptions = getOrgSelectOptions(organizations); - const practOptions = getPractitionerSelectOptions(practitioners); + const [selectedOrgs, setSelectedOrgs] = useState[]>([]); + const [selectedPractitioners, setSelectedPractitioners] = useState[]>( + [] + ); const statusOptions = [ { label: t('Active'), value: 'active' }, @@ -62,6 +63,29 @@ const CareTeamForm: React.FC = (props: CareTeamFormProps) => }, }; + const organizationPreloadExistingOptions = preloadExistingOptionsFactory( + fhirBaseURL, + processOrganizationOption + ); + const practitionerPreloadExistingOptions = preloadExistingOptionsFactory( + fhirBaseURL, + processPractitionerOption + ); + + const organizationChangeHandler = ( + orgs: SelectOption | SelectOption[] + ) => { + const sanitized = Array.isArray(orgs) ? orgs : [orgs]; + setSelectedOrgs(sanitized); + }; + + const practitionerChangeHandler = ( + practitioners: SelectOption | SelectOption[] + ) => { + const sanitized = Array.isArray(practitioners) ? practitioners : [practitioners]; + setSelectedPractitioners(sanitized); + }; + return ( @@ -73,7 +97,10 @@ const CareTeamForm: React.FC = (props: CareTeamFormProps) => initialValues={initialValues} onFinish={(values: FormFields) => { setIsSubmitting(true); - submitForm(values, initialValues, fhirBaseURL, organizations, practitioners, t) + submitForm(values, initialValues, fhirBaseURL, selectedOrgs, selectedPractitioners, t) + .then(() => { + history.push(URL_CARE_TEAM); + }) .catch(() => { if (initialValues.id) { sendErrorNotification(t('There was a problem updating the Care Team')); @@ -117,14 +144,17 @@ const CareTeamForm: React.FC = (props: CareTeamFormProps) => id="practitionerParticipants" label={t('Practitioner Participant')} > - + baseUrl={fhirBaseURL} + resourceType={organizationResourceType} + transformOption={processOrganizationOption} mode="multiple" allowClear showSearch placeholder={t('Select a managing Organization')} - filterOption={selectFilterFunction} disabled={disabled?.includes(managingOrganizations)} + getFullOptionOnChange={organizationChangeHandler} + discoverUnknownOptions={organizationPreloadExistingOptions} /> diff --git a/packages/fhir-care-team/src/components/CreateEditCareTeam/index.tsx b/packages/fhir-care-team/src/components/CreateEditCareTeam/index.tsx index 1c3482ec8..75425081b 100644 --- a/packages/fhir-care-team/src/components/CreateEditCareTeam/index.tsx +++ b/packages/fhir-care-team/src/components/CreateEditCareTeam/index.tsx @@ -4,27 +4,17 @@ import { useQuery } from 'react-query'; import { Spin } from 'antd'; import { sendErrorNotification } from '@opensrp/notifications'; import { RouteComponentProps, useParams } from 'react-router-dom'; -import { - BrokenPage, - FHIRServiceClass, - getResourcesFromBundle, - loadAllResources, -} from '@opensrp/react-utils'; +import { BrokenPage, FHIRServiceClass } from '@opensrp/react-utils'; import { FHIR_CARE_TEAM, - FHIR_PRACTITIONERS, managingOrganizations, - organizationResourceType, practitionerParticipants, - practitionerResourceType, ROUTE_PARAM_CARE_TEAM_ID, } from '../../constants'; import { CareTeamForm } from './Form'; import { getCareTeamFormFields } from './utils'; import { useTranslation } from '../../mls'; -import { IPractitioner } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IPractitioner'; import { ICareTeam } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/ICareTeam'; -import { IOrganization } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IOrganization'; import { useUserRole } from '@opensrp/rbac'; // Interface for route params @@ -63,34 +53,10 @@ const CreateEditCareTeam: React.FC = (props: CreateEdit staleTime: 0, } ); - const hasReadOrgs = userRole.hasPermissions(['Organization.read']); - const organizations = useQuery( - organizationResourceType, - async () => loadAllResources(fhirBaseURL, organizationResourceType), - { - onError: () => sendErrorNotification(t('There was a problem fetching organizations')), - select: (res) => getResourcesFromBundle(res), - enabled: hasReadOrgs, - } - ); - const hasReadPractitioner = userRole.hasPermissions(['Practitioner.read']); - const fhirPractitioners = useQuery( - FHIR_PRACTITIONERS, - async () => loadAllResources(fhirBaseURL, practitionerResourceType, { active: true }), - { - onError: () => sendErrorNotification(t('There was a problem fetching practitioners')), - select: (res) => getResourcesFromBundle(res), - enabled: hasReadPractitioner, - } - ); - if ( - fhirPractitioners.isLoading || - organizations.isLoading || - (!singleCareTeam.isIdle && singleCareTeam.isLoading) - ) { + if (!singleCareTeam.isIdle && singleCareTeam.isLoading) { return ; } @@ -110,8 +76,6 @@ const CreateEditCareTeam: React.FC = (props: CreateEdit const careTeamFormProps = { fhirBaseURL, initialValues: buildInitialValues, - organizations: organizations.data ?? [], - practitioners: fhirPractitioners.data ?? [], disabled: disabledFields, }; diff --git a/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/__snapshots__/form.test.tsx.snap b/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/__snapshots__/form.test.tsx.snap deleted file mode 100644 index c8a389fd6..000000000 --- a/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/__snapshots__/form.test.tsx.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/forms/CreateTeamForm adds new care team successfully: sd 1`] = `null`; diff --git a/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/fixtures.ts b/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/fixtures.ts index f8d47d26b..39cc474e4 100644 --- a/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/fixtures.ts +++ b/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/fixtures.ts @@ -137,15 +137,23 @@ export const careTeam4201Edited = { }, ], name: 'Peter Charlmers Care teamWho is Peter Charlmers', - subject: { reference: 'Patient/4195' }, + subject: { reference: 'Patient/4195', display: 'Peter James Chalmers' }, encounter: { reference: 'Encounter/4197' }, period: { end: '2013-01-01' }, + managingOrganization: [], + identifier: [{ use: 'official', value: '0b3a3311-6f5a-40dd-95e5-008001acebe1' }], participant: [ - { member: { reference: 'Patient/4195', display: '' } }, - { member: { reference: '#pr1', display: '' } }, + { + role: [{ text: 'responsiblePerson' }], + member: { reference: 'Patient/4195', display: 'Peter James Chalmers' }, + }, + { + role: [{ text: 'responsiblePerson' }], + member: { reference: '#pr1', display: 'Dorothy Dietition' }, + onBehalfOf: { reference: 'Organization/f001' }, + period: { end: '2013-01-01' }, + }, ], - managingOrganization: [{ reference: 'Organization/4190' }], - identifier: [{ use: 'official', value: '0b3a3311-6f5a-40dd-95e5-008001acebe1' }], }; export const careTeam4201alternative = { diff --git a/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/form.test.tsx b/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/form.test.tsx index 4f49028d0..036b74952 100644 --- a/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/form.test.tsx +++ b/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/form.test.tsx @@ -1,242 +1,235 @@ import React from 'react'; -import { mount } from 'enzyme'; -import flushPromises from 'flush-promises'; +import { CareTeamForm } from '../Form'; +import { defaultInitialValues, getCareTeamFormFields } from '../utils'; +import { cleanup, fireEvent, waitFor, render, screen } from '@testing-library/react'; +import userEvents from '@testing-library/user-event'; +import * as notifications from '@opensrp/notifications'; import nock from 'nock'; -import { history } from '@onaio/connected-reducer-registry'; -import * as fhirCient from 'fhirclient'; -import * as fixtures from './fixtures'; -import { act } from 'react-dom/test-utils'; +import { + careTeamResourceType, + organizationResourceType, + practitionerResourceType, +} from '../../../constants'; +import { + createdCareTeam2, + careTeam4201alternativeEdited, + organizations, + practitioners, + careTeam4201alternative, +} from './fixtures'; +import { store } from '@opensrp/store'; +import { authenticateUser } from '@onaio/session-reducer'; +import { QueryClientProvider, QueryClient } from 'react-query'; import { Router } from 'react-router'; -import { CareTeamForm } from '../Form'; -import { defaultInitialValues } from '../utils'; -import Client from 'fhirclient/lib/Client'; -import { getResourcesFromBundle } from '@opensrp/react-utils'; -import toJson from 'enzyme-to-json'; +import { createMemoryHistory } from 'history'; -/* eslint-disable @typescript-eslint/naming-convention */ +jest.mock('@opensrp/notifications', () => ({ + __esModule: true, + ...Object.assign({}, jest.requireActual('@opensrp/notifications')), +})); -jest.mock('antd', () => { - const antd = jest.requireActual('antd'); +jest.mock('fhirclient', () => { + return jest.requireActual('fhirclient/lib/entry/browser'); +}); - /* eslint-disable react/prop-types */ - const Select = ({ children, onChange }) => { - return ; +jest.mock('uuid', () => { + const actual = jest.requireActual('uuid'); + return { + ...actual, + v4: () => '9b782015-8392-4847-b48c-50c11638656b', }; +}); - const Option = ({ children, ...otherProps }) => { - return ; - }; - /* eslint-disable react/prop-types */ +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + cacheTime: 0, + }, + }, +}); - Select.Option = Option; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const AppWrapper = ({ children }: { children: any }) => { + const history = createMemoryHistory(); + return ( + + {children} + + ); +}; + +beforeAll(() => { + nock.disableNetConnect(); + store.dispatch( + authenticateUser( + true, + { + email: 'bob@example.com', + name: 'Bobbie', + username: 'RobertBaratheon', + }, + { api_token: 'hunter2', oAuth2Data: { access_token: 'sometoken', state: 'abcde' } } + ) + ); +}); - return { - __esModule: true, - ...antd, - Select, - }; +afterAll(() => { + nock.enableNetConnect(); }); -jest.setTimeout(10000); +afterEach(() => { + nock.cleanAll(); + cleanup(); + jest.resetAllMocks(); +}); -jest.mock('@opensrp/notifications', () => ({ - __esModule: true, - ...Object.assign({}, jest.requireActual('@opensrp/notifications')), -})); +const props = { + initialValues: defaultInitialValues, + fhirBaseURL: 'https://r4.smarthealthit.org/', +}; + +test('1157 - Create care team works corectly', async () => { + const successNoticeMock = jest + .spyOn(notifications, 'sendSuccessNotification') + .mockImplementation(() => undefined); + + const preloadScope = nock(props.fhirBaseURL) + .get(`/${organizationResourceType}/_search`) + .query({ _getpagesoffset: '0', _count: '20' }) + .reply(200, organizations) + .get(`/${practitionerResourceType}/_search`) + .query({ _getpagesoffset: '0', _count: '20' }) + .reply(200, practitioners); + + nock(props.fhirBaseURL) + .put(`/${careTeamResourceType}/${createdCareTeam2.id}`, createdCareTeam2) + .reply(200) + .persist(); + + render( + + + + ); + + await waitFor(() => { + expect(preloadScope.pendingMocks()).toEqual([]); + }); + await waitFor(() => { + expect(screen.getByText(/Create Care Team/)).toBeInTheDocument(); + }); -describe('components/forms/CreateTeamForm', () => { - const props = { - initialValues: defaultInitialValues, - fhirBaseURL: 'https://r4.smarthealthit.org/', - practitioners: getResourcesFromBundle(fixtures.practitioners), - organizations: getResourcesFromBundle(fixtures.organizations), - }; + const nameInput = screen.getByLabelText('Name') as Element; + userEvents.type(nameInput, 'care team'); - afterEach(() => { - jest.resetAllMocks(); - }); + const activeStatusRadio = screen.getByLabelText('Active'); + expect(activeStatusRadio).toBeChecked(); - it('form validation works for required fields', async () => { - const wrapper = mount(); + const inactiveStatusRadio = screen.getByLabelText('Inactive'); + expect(inactiveStatusRadio).not.toBeChecked(); + userEvents.click(inactiveStatusRadio); - wrapper.find('form').simulate('submit'); + const practitionersInput = screen.getByLabelText('Practitioner Participant'); + fireEvent.mouseDown(practitionersInput); + fireEvent.click(screen.getByTitle('Ward N 2 Williams MD')); - await act(async () => { - await flushPromises(); - }); + const managingOrgsSelect = screen.getByLabelText('Managing organizations'); + fireEvent.mouseDown(managingOrgsSelect); + fireEvent.click(screen.getByTitle('Test Team 70')); - wrapper.update(); - await act(async () => { - await flushPromises(); - }); - // name is required and has no default - expect(wrapper.find('#name .ant-form-item').text()).toMatchInlineSnapshot( - `"NameName is Required"` - ); - wrapper.unmount(); - }); + const saveBtn = screen.getByRole('button', { name: 'Save' }); + userEvents.click(saveBtn); - it('adds new care team successfully', async () => { - nock('https://fhir.smarthealthit.org/') - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .post('/CareTeam', (body: any) => { - expect(body).toMatchObject({ - id: '308', - identifier: [{ use: 'official', value: '93bc9c3d-6321-41b0-9b93-1275d7114e22' }], - meta: { - lastUpdated: '2021-06-18T06:07:29.649+00:00', - source: '#9bf085bac3f61473', - versionId: '4', - }, - name: 'Care Team One', - participant: [ - { member: { reference: 'Practitioner/206' } }, - { member: { reference: 'Practitioner/103' } }, - ], - resourceType: 'CareTeam', - status: 'active', - subject: { reference: 'Group/306' }, - }); - return true; - }) - .reply(200, 'CareTeam created successfully'); - - const wrapper = mount(); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const client = new Client({} as any, { - serverUrl: 'https://fhir.smarthealthit.org/', - }); - - const result = await client.create(fixtures.careTeam1); - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - - // ensure the post is made against the correct resource endpoint - expect(result.url).toEqual('https://fhir.smarthealthit.org/CareTeam'); - - // set team name - const nameInput = wrapper.find('input#name'); - nameInput.simulate('change', { target: { name: 'name', value: 'Care Team Test' } }); - - // set form fields - wrapper - .find('input#name') - .simulate('change', { target: { name: 'name', value: 'Care Team Test' } }); - wrapper - .find('input[type="radio"]') - .first() - .simulate('change', { target: { name: 'status', checked: true } }); - wrapper - .find('select') - .first() - .simulate('change', { - target: { value: ['Practitioner A'] }, - }); - - const sd = wrapper.find('#practitionersId'); - expect(toJson(sd)).toMatchSnapshot('sd'); - - wrapper.find('form').simulate('submit'); - - await act(async () => { - await flushPromises(); - }); - - wrapper.update(); - - expect(wrapper.find('form').text()).toMatchInlineSnapshot( - `"IDUUIDNameStatusActiveInactivePractitioner ParticipantManaging organizationsSaveCancel"` - ); - wrapper.unmount(); + await waitFor(() => { + expect(successNoticeMock.mock.calls).toEqual([['Successfully added CareTeams']]); }); - it('edits care team', async () => { - const propEdit = { - ...props, - initialValues: { - uuid: '93bc9c3d-6321-41b0-9b93-1275d7114e22', - id: '308', - name: 'Care Team One', - status: 'active', - practitionersId: ['206', '103'], - groupsId: '306', - }, - }; - - const wrapper = mount(); - - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - // usergroup name - await act(async () => { - const nameInput = wrapper.find('input#name'); - nameInput.simulate('change', { target: { name: 'name', value: 'Care Team Test1' } }); - }); - wrapper.update(); - - wrapper.find('form').simulate('submit'); - - await act(async () => { - await flushPromises(); - }); - wrapper.update(); - expect(document.getElementsByClassName('ant-notification')).toHaveLength(1); - wrapper.unmount(); + expect(nock.isDone()).toBeTruthy(); +}); + +test('1157 - editing care team works corectly', async () => { + const thisProps = { + ...props, + initialValues: getCareTeamFormFields(careTeam4201alternative), + }; + const successNoticeMock = jest + .spyOn(notifications, 'sendSuccessNotification') + .mockImplementation(() => undefined); + + const preloadScope = nock(props.fhirBaseURL) + .get(`/${organizationResourceType}/_search`) + .query({ _getpagesoffset: '0', _count: '20' }) + .reply(200, organizations) + .get(`/${practitionerResourceType}/_search`) + .query({ _getpagesoffset: '0', _count: '20' }) + .reply(200, practitioners) + .post(`/`, { + resourceType: 'Bundle', + type: 'batch', + entry: [{ request: { method: 'GET', url: 'Organization/368' } }], + }) + .reply(200, []) + .post(`/`, { + resourceType: 'Bundle', + type: 'batch', + entry: [{ request: { method: 'GET', url: 'Practitioner/102' } }], + }) + .reply(200, []); + + nock(props.fhirBaseURL) + .put( + `/${careTeamResourceType}/${careTeam4201alternativeEdited.id}`, + careTeam4201alternativeEdited + ) + .reply(200) + .persist(); + + render( + + + + ); + + await waitFor(() => { + expect(preloadScope.pendingMocks()).toEqual([]); }); + await waitFor(() => { + expect(screen.getByText(/Edit Care Team /)).toBeInTheDocument(); + }); + + const nameInput = screen.getByLabelText('Name') as Element; + userEvents.type(nameInput, 'care team'); + + const activeStatusRadio = screen.getByLabelText('Active'); + expect(activeStatusRadio).toBeChecked(); - it('Care Team is not created if api is down', async () => { - const wrapper = mount(); - - await act(async () => { - await flushPromises(); - }); - - wrapper.update(); - - // set usersgroup name - const nameInput = wrapper.find('input#name'); - nameInput.simulate('change', { target: { name: 'name', value: 'Test' } }); - - wrapper.find('form').simulate('submit'); - const fhir = jest.spyOn(fhirCient, 'client'); - fhir.mockImplementation( - jest.fn().mockImplementation(() => { - return { - request: jest.fn().mockRejectedValue('API is down'), - }; - }) - ); - await act(async () => { - wrapper.update(); - }); - - await flushPromises(); - wrapper.update(); - expect(document.getElementsByClassName('ant-notification')).toHaveLength(1); - wrapper.unmount(); + const inactiveStatusRadio = screen.getByLabelText('Inactive'); + expect(inactiveStatusRadio).not.toBeChecked(); + userEvents.click(inactiveStatusRadio); + + // remove assigned + const selectClear = [...document.querySelectorAll('.ant-select-selection-item-remove')]; + expect(selectClear).toHaveLength(2); + selectClear.forEach((clear) => { + fireEvent.click(clear); }); - it('cancel button returns user to list view', async () => { - const historyPushMock = jest.spyOn(history, 'push'); - const wrapper = mount( - - - - ); - - await act(async () => { - await flushPromises(); - }); - - wrapper.update(); - wrapper.find('.cancel-care-team').at(1).simulate('click'); - wrapper.update(); - expect(historyPushMock).toHaveBeenCalledTimes(1); - expect(historyPushMock).toHaveBeenCalledWith('/admin/CareTeams'); + const practitionersInput = screen.getByLabelText('Practitioner Participant'); + fireEvent.mouseDown(practitionersInput); + + fireEvent.click(screen.getByTitle('Ward N 1 Williams MD')); + + const managingOrgsSelect = screen.getByLabelText('Managing organizations'); + fireEvent.mouseDown(managingOrgsSelect); + fireEvent.click(screen.getByTitle('testing ash123')); + + const saveBtn = screen.getByRole('button', { name: 'Save' }); + userEvents.click(saveBtn); + + await waitFor(() => { + expect(successNoticeMock.mock.calls).toEqual([['Successfully updated CareTeams']]); }); + + expect(nock.isDone()).toBeTruthy(); }); diff --git a/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/form.unmocked.test.tsx b/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/form.unmocked.test.tsx deleted file mode 100644 index 51954db35..000000000 --- a/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/form.unmocked.test.tsx +++ /dev/null @@ -1,309 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import flushPromises from 'flush-promises'; -import { act } from 'react-dom/test-utils'; -import { CareTeamForm } from '../Form'; -import { defaultInitialValues, getCareTeamFormFields } from '../utils'; -import { getResourcesFromBundle } from '@opensrp/react-utils'; -import { cleanup, fireEvent, waitFor, render, screen } from '@testing-library/react'; -import userEvents from '@testing-library/user-event'; -import * as notifications from '@opensrp/notifications'; -import nock from 'nock'; -import { careTeamResourceType } from '../../../constants'; -import { - createdCareTeam, - createdCareTeam2, - careTeam4201alternativeEdited, - organizations, - practitioners, - careTeam4201alternative, -} from './fixtures'; -import { store } from '@opensrp/store'; -import { authenticateUser } from '@onaio/session-reducer'; - -jest.mock('@opensrp/notifications', () => ({ - __esModule: true, - ...Object.assign({}, jest.requireActual('@opensrp/notifications')), -})); - -jest.mock('fhirclient', () => { - return jest.requireActual('fhirclient/lib/entry/browser'); -}); - -jest.mock('uuid', () => { - const actual = jest.requireActual('uuid'); - return { - ...actual, - v4: () => '9b782015-8392-4847-b48c-50c11638656b', - }; -}); - -beforeAll(() => { - nock.disableNetConnect(); - store.dispatch( - authenticateUser( - true, - { - email: 'bob@example.com', - name: 'Bobbie', - username: 'RobertBaratheon', - }, - { api_token: 'hunter2', oAuth2Data: { access_token: 'sometoken', state: 'abcde' } } - ) - ); -}); - -afterAll(() => { - nock.enableNetConnect(); -}); - -afterEach(() => { - nock.cleanAll(); - cleanup(); - jest.resetAllMocks(); -}); - -const props = { - initialValues: defaultInitialValues, - fhirBaseURL: 'https://r4.smarthealthit.org/', - practitioners: getResourcesFromBundle(practitioners), - organizations: getResourcesFromBundle(organizations), -}; - -test('filter select by text able to create new careteam', async () => { - const container = document.createElement('div'); - document.body.appendChild(container); - - const successNoticeMock = jest - .spyOn(notifications, 'sendSuccessNotification') - .mockImplementation(() => undefined); - - nock(props.fhirBaseURL) - .put(`/${careTeamResourceType}/${createdCareTeam.id}`, createdCareTeam) - .reply(200) - .persist(); - - const wrapper = mount(, { attachTo: container }); - - await act(async () => { - await flushPromises(); - wrapper.update(); - }); - - // simulate active change - wrapper - .find('#status .ant-form-item input') - .first() - .simulate('change', { - target: { checked: true }, - }); - - // simulate name change - wrapper - .find('#name .ant-form-item input') - .simulate('change', { target: { name: 'name', value: 'Care team 1' } }); - - // simulate value selection for type - wrapper.find('input#practitionerParticipants').simulate('mousedown'); - - let optionTexts = [ - ...document.querySelectorAll( - '#practitionerParticipants_list+div.rc-virtual-list .ant-select-item-option-content' - ), - ].map((option) => { - return option.textContent; - }); - - expect(optionTexts).toHaveLength(12); - expect(optionTexts).toEqual([ - 'Ward N 2 Williams MD', - 'Ward N 1 Williams MD', - 'Ward N Williams MD', - 'test fhir', - 'test fhir', - 'test fhir', - 'test fhir', - 'test fhir', - 'test fhir', - 'test fhir', - 'test fhir', - 'test fhir', - ]); - - // filter searching through members works - await userEvents.type(document.querySelector('input#practitionerParticipants'), 'Ward'); - - // options after search - let afterFilterOptionTexts = [ - ...document.querySelectorAll( - '#practitionerParticipants_list+div.rc-virtual-list .ant-select-item-option-content' - ), - ].map((option) => { - return option.textContent; - }); - - expect(afterFilterOptionTexts).toEqual([ - 'Ward N 2 Williams MD', - 'Ward N 1 Williams MD', - 'Ward N Williams MD', - ]); - - fireEvent.click(document.querySelector('[title="Ward N 2 Williams MD"]')); - - // simulate value selection for type - wrapper.find('input#managingOrganizations').simulate('mousedown'); - - optionTexts = [ - ...document.querySelectorAll( - '#managingOrganizations_list+div.rc-virtual-list .ant-select-item-option-content' - ), - ].map((option) => { - return option.textContent; - }); - - expect(optionTexts).toHaveLength(12); - expect(optionTexts).toEqual([ - 'Test Team 5', - 'Test Team 5', - 'Test Team 5', - 'Test Team One', - 'Test UUID 46', - 'Test Team 70', - 'test123', - 'testing ash123', - 'ashfahan test 1', - 'ashfahan test 2', - 'ashfahan test 2', - 'ashfahan test 2', - ]); - - // filter searching through members works - await userEvents.type(document.querySelector('input#managingOrganizations'), '70'); - - // options after search - afterFilterOptionTexts = [ - ...document.querySelectorAll( - '#managingOrganizations_list+div.rc-virtual-list .ant-select-item-option-content' - ), - ].map((option) => { - return option.textContent; - }); - - expect(afterFilterOptionTexts).toEqual(['Test Team 70']); - - fireEvent.click(document.querySelector('[title="Test Team 70"]')); - - await flushPromises(); - wrapper.update(); - - wrapper.find('form').simulate('submit'); - - await waitFor(() => { - expect(successNoticeMock.mock.calls).toEqual([['Successfully added CareTeams']]); - }); - - expect(nock.isDone()).toBeTruthy(); - wrapper.unmount(); -}); - -test('1157 - Create care team works corectly', async () => { - const successNoticeMock = jest - .spyOn(notifications, 'sendSuccessNotification') - .mockImplementation(() => undefined); - - nock(props.fhirBaseURL) - .put(`/${careTeamResourceType}/${createdCareTeam2.id}`, createdCareTeam2) - .reply(200) - .persist(); - - render(); - - await waitFor(() => { - expect(screen.getByText(/Create Care Team/)).toBeInTheDocument(); - }); - - const nameInput = screen.getByLabelText('Name') as Element; - userEvents.type(nameInput, 'care team'); - - const activeStatusRadio = screen.getByLabelText('Active'); - expect(activeStatusRadio).toBeChecked(); - - const inactiveStatusRadio = screen.getByLabelText('Inactive'); - expect(inactiveStatusRadio).not.toBeChecked(); - userEvents.click(inactiveStatusRadio); - - const practitionersInput = screen.getByLabelText('Practitioner Participant'); - userEvents.click(practitionersInput); - fireEvent.click(screen.getByTitle('Ward N 2 Williams MD')); - - const managingOrgsSelect = screen.getByLabelText('Managing organizations'); - userEvents.click(managingOrgsSelect); - fireEvent.click(screen.getByTitle('Test Team 70')); - - const saveBtn = screen.getByRole('button', { name: 'Save' }); - userEvents.click(saveBtn); - - await waitFor(() => { - expect(successNoticeMock.mock.calls).toEqual([['Successfully added CareTeams']]); - }); - - expect(nock.isDone()).toBeTruthy(); -}); - -test('1157 - editing care team works corectly', async () => { - const thisProps = { - ...props, - initialValues: getCareTeamFormFields(careTeam4201alternative), - }; - const successNoticeMock = jest - .spyOn(notifications, 'sendSuccessNotification') - .mockImplementation(() => undefined); - - nock(props.fhirBaseURL) - .put( - `/${careTeamResourceType}/${careTeam4201alternativeEdited.id}`, - careTeam4201alternativeEdited - ) - .reply(200) - .persist(); - - render(); - - await waitFor(() => { - expect(screen.getByText(/Edit Care Team /)).toBeInTheDocument(); - }); - - const nameInput = screen.getByLabelText('Name') as Element; - userEvents.type(nameInput, 'care team'); - - const activeStatusRadio = screen.getByLabelText('Active'); - expect(activeStatusRadio).toBeChecked(); - - const inactiveStatusRadio = screen.getByLabelText('Inactive'); - expect(inactiveStatusRadio).not.toBeChecked(); - userEvents.click(inactiveStatusRadio); - - // remove assigned - const selectClear = [...document.querySelectorAll('.ant-select-selection-item-remove')]; - expect(selectClear).toHaveLength(2); - selectClear.forEach((clear) => { - fireEvent.click(clear); - }); - - const practitionersInput = screen.getByLabelText('Practitioner Participant'); - userEvents.click(practitionersInput); - fireEvent.click(screen.getByTitle('Ward N 1 Williams MD')); - - const managingOrgsSelect = screen.getByLabelText('Managing organizations'); - userEvents.click(managingOrgsSelect); - fireEvent.click(screen.getByTitle('testing ash123')); - - const saveBtn = screen.getByRole('button', { name: 'Save' }); - userEvents.click(saveBtn); - - await waitFor(() => { - expect(successNoticeMock.mock.calls).toEqual([['Successfully updated CareTeams']]); - }); - - expect(nock.isDone()).toBeTruthy(); -}); diff --git a/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/index.test.tsx b/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/index.test.tsx index e3a91b566..403dc4865 100644 --- a/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/index.test.tsx +++ b/packages/fhir-care-team/src/components/CreateEditCareTeam/tests/index.test.tsx @@ -24,14 +24,19 @@ import { import { screen, render } from '@testing-library/react'; import userEvents from '@testing-library/user-event'; import { careTeam1, careTeam4201Edited, organizations } from './fixtures'; -import flushPromises from 'flush-promises'; import { RoleContext } from '@opensrp/rbac'; import { superUserRole } from '@opensrp/react-utils'; +import * as notifications from '@opensrp/notifications'; jest.mock('fhirclient', () => { return jest.requireActual('fhirclient/lib/entry/browser'); }); +jest.mock('@opensrp/notifications', () => ({ + __esModule: true, + ...Object.assign({}, jest.requireActual('@opensrp/notifications')), +})); + const mockId = '0b3a3311-6f5a-40dd-95e5-008001acebe1'; jest.mock('uuid', () => { @@ -103,18 +108,12 @@ test('renders correctly for create care team', async () => { nock(props.fhirBaseURL) .get(`/${organizationResourceType}/_search`) - .query({ _summary: 'count' }) - .reply(200, { total: 1000 }) - .get(`/${organizationResourceType}/_search`) - .query({ _count: 1000 }) + .query({ _getpagesoffset: '0', _count: '20' }) .reply(200, organizations); nock(props.fhirBaseURL) .get(`/${practitionerResourceType}/_search`) - .query({ _summary: 'count', active: true }) - .reply(200, { total: 1000 }) - .get(`/${practitionerResourceType}/_search`) - .query({ _count: 1000, active: true }) + .query({ _getpagesoffset: '0', _count: '20' }) .reply(200, fixtures.practitioners); render( @@ -123,8 +122,6 @@ test('renders correctly for create care team', async () => { ); - await waitForElementToBeRemoved(document.querySelector('.ant-spin')); - await waitFor(() => { expect(nock.pendingMocks()).toEqual([]); }); @@ -171,28 +168,31 @@ test('renders correctly for edit care team', async () => { nock(props.fhirBaseURL) .get(`/${organizationResourceType}/_search`) - .query({ _summary: 'count' }) - .reply(200, { total: 1000 }) - .get(`/${organizationResourceType}/_search`) - .query({ _count: 1000 }) + .query({ _getpagesoffset: '0', _count: '20' }) .reply(200, organizations); nock(props.fhirBaseURL) .get(`/${practitionerResourceType}/_search`) - .query({ _summary: 'count', active: true }) - .reply(200, { total: 1000 }) - .get(`/${practitionerResourceType}/_search`) - .query({ _count: 1000, active: true }) + .query({ _getpagesoffset: '0', _count: '20' }) .reply(200, fixtures.practitioners); + nock(props.fhirBaseURL) + .post(`/`, { + resourceType: 'Bundle', + type: 'batch', + entry: [ + { request: { method: 'GET', url: 'Practitioner/206' } }, + { request: { method: 'GET', url: 'Practitioner/103' } }, + ], + }) + .reply(200, []); + render( ); - await waitForElementToBeRemoved(document.querySelector('.ant-spin')); - await waitFor(() => { expect(nock.pendingMocks()).toEqual([]); }); @@ -204,6 +204,11 @@ test('renders correctly for edit care team', async () => { test('#1016 - does not create malformed request body', async () => { const history = createMemoryHistory(); + + const successNoticeMock = jest + .spyOn(notifications, 'sendSuccessNotification') + .mockImplementation(() => undefined); + const careTeamId = fixtures.careTeam4201.id; history.push(`/add/${careTeamId}`); @@ -213,20 +218,22 @@ test('#1016 - does not create malformed request body', async () => { nock(props.fhirBaseURL) .get(`/${organizationResourceType}/_search`) - .query({ _summary: 'count' }) - .reply(200, { total: 1000 }) - .get(`/${organizationResourceType}/_search`) - .query({ _count: 1000 }) + .query({ _getpagesoffset: '0', _count: '20' }) .reply(200, organizations); nock(props.fhirBaseURL) .get(`/${practitionerResourceType}/_search`) - .query({ _summary: 'count', active: true }) - .reply(200, { total: 1000 }) - .get(`/${practitionerResourceType}/_search`) - .query({ _count: 1000, active: true }) + .query({ _getpagesoffset: '0', _count: '20' }) .reply(200, fixtures.practitioners); + nock(props.fhirBaseURL) + .post(`/`, { + resourceType: 'Bundle', + type: 'batch', + entry: [{ request: { method: 'GET', url: 'Organization/4190' } }], + }) + .reply(200, []); + render( @@ -253,7 +260,10 @@ test('#1016 - does not create malformed request body', async () => { // submit await act(async () => { userEvents.click(screen.getByText(/Save/)); - await flushPromises(); + }); + + await waitFor(() => { + expect(successNoticeMock).toHaveBeenCalledWith('Successfully updated CareTeams'); }); }); diff --git a/packages/fhir-care-team/src/components/CreateEditCareTeam/utils.tsx b/packages/fhir-care-team/src/components/CreateEditCareTeam/utils.tsx index b542b1cf4..5e747725f 100644 --- a/packages/fhir-care-team/src/components/CreateEditCareTeam/utils.tsx +++ b/packages/fhir-care-team/src/components/CreateEditCareTeam/utils.tsx @@ -1,12 +1,14 @@ -import { history } from '@onaio/connected-reducer-registry'; import { v4 } from 'uuid'; import { FHIRServiceClass, getObjLike, + getResourcesFromBundle, IdentifierUseCodes, parseFhirHumanName, + SelectOption, + TransformOptions, } from '@opensrp/react-utils'; -import { sendErrorNotification, sendSuccessNotification } from '@opensrp/notifications'; +import { sendSuccessNotification } from '@opensrp/notifications'; import { FHIR_CARE_TEAM, id, @@ -14,7 +16,6 @@ import { organizationResourceType, practitionerParticipants, practitionerResourceType, - URL_CARE_TEAM, uuid, name, status, @@ -23,12 +24,13 @@ import { import { IfhirR4 } from '@smile-cdr/fhirts'; import type { TFunction } from '@opensrp/i18n'; import { ICareTeam } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/ICareTeam'; -import { get, keyBy } from 'lodash'; +import { get } from 'lodash'; import { IOrganization } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IOrganization'; import { HumanName } from '@smile-cdr/fhirts/dist/FHIR-R4/classes/humanName'; import { IPractitioner } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IPractitioner'; +import { IBundle } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IBundle'; +import { IResource } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IResource'; import { CareTeamParticipant } from '@smile-cdr/fhirts/dist/FHIR-R4/classes/careTeamParticipant'; -import { Reference } from '@smile-cdr/fhirts/dist/FHIR-R4/classes/reference'; /** * computes participants that should be carried over when generating the payload @@ -70,34 +72,16 @@ export const submitForm = async ( values: FormFields, initialValues: FormFields, fhirBaseURL: string, - organizations: IOrganization[], - practitioners: IPractitioner[], + selectedOrganizations: SelectOption[], + selectedPractitioners: SelectOption[], t: TFunction ): Promise => { const { initialCareTeam, id, uuid } = initialValues; const { meta, text, participant, ...nonMetaFields } = initialCareTeam ?? {}; - const allPractitionersById = keyBy( - practitioners, - (practitioner) => `${practitionerResourceType}/${practitioner.id}` - ); - const practitionerParticipantsById: Record = {}; - values[practitionerParticipants].forEach((id) => { - const fullPractitionerObj = allPractitionersById[id]; - practitionerParticipantsById[id] = { - member: { - reference: id, - display: getPatientName(fullPractitionerObj), - }, - }; - }); - - const organizationsById = keyBy(organizations, (org) => `${organizationResourceType}/${org.id}`); - const managingOrgsById: Record = {}; - values[managingOrganizations].forEach((id) => { - const orgName = (organizationsById[id] as IOrganization | undefined)?.name; - const orgDisplay = orgName ? { display: organizationsById[id].name } : {}; - managingOrgsById[id] = { + const participatingOrgsPayload = selectedOrganizations.map((orgOption) => { + const { label, value } = orgOption; + return { role: [ { coding: [ @@ -110,22 +94,30 @@ export const submitForm = async ( }, ], member: { - ...orgDisplay, - reference: id, + reference: value as string, + display: label, }, }; }); - const carriedOverParticipantsById = getCarriedOverParticipants(values, initialValues); - const finalParticipantsById = { - ...carriedOverParticipantsById, - ...practitionerParticipantsById, - ...managingOrgsById, - }; + const practitionerParticipants = selectedPractitioners.map((option) => { + const { label, value } = option; + return { + member: { + reference: value as string, + display: label, + }, + }; + }); - const managingOrgsPayload = Object.values(managingOrgsById).map( - (obj) => obj.member - ) as Reference[]; + const carriedOverParticipantsById = getCarriedOverParticipants(values, initialValues); + const carriedOverParticipants = Object.values(carriedOverParticipantsById); + const managingOrgsReferences = participatingOrgsPayload.map((payload) => payload.member); + const allParticipants = [ + ...carriedOverParticipants, + ...practitionerParticipants, + ...participatingOrgsPayload, + ]; const careTeamId = uuid ? uuid : v4(); const payload: Omit = { @@ -140,8 +132,8 @@ export const submitForm = async ( id: id ? id : careTeamId, name: values.name, status: values.status as IfhirR4.CareTeam.StatusEnum, - participant: Object.values(finalParticipantsById), - managingOrganization: managingOrgsPayload, + participant: allParticipants, + managingOrganization: managingOrgsReferences, }; const serve = new FHIRServiceClass(fhirBaseURL, FHIR_CARE_TEAM); @@ -149,14 +141,10 @@ export const submitForm = async ( if (id) { successNotificationMessage = t('Successfully updated CareTeams'); } - await serve + return await serve .update(payload) // TODO - possible place to use translation plurals - .then(() => sendSuccessNotification(successNotificationMessage)) - .catch(() => { - sendErrorNotification(t('There was a problem fetching the Care Team')); - }) - .finally(() => history.push(URL_CARE_TEAM)); + .then(() => sendSuccessNotification(successNotificationMessage)); }; /** @@ -233,24 +221,6 @@ export interface SelectOptions { label?: string; } -export const getOrgSelectOptions = (orgs: IOrganization[] = []): SelectOptions[] => { - return orgs.map((org) => { - return { - value: `${organizationResourceType}/${org.id}`, - label: org.name, - }; - }); -}; - -export const getPractitionerSelectOptions = (resources: IPractitioner[] = []): SelectOptions[] => { - return resources.map((res) => { - return { - value: `${practitionerResourceType}/${res.id}`, - label: getPatientName(res), - }; - }); -}; - /** * filter practitioners select on search * @@ -260,3 +230,71 @@ export const getPractitionerSelectOptions = (resources: IPractitioner[] = []): S export const selectFilterFunction = (inputValue: string, option?: SelectOptions) => { return !!option?.label?.toLowerCase().includes(inputValue.toLowerCase()); }; + +/** + * creates util function that given a set of resource ids, it can fetch + * just those resources whose id are provided + * + * @param fhirBaseUrl - fhir base url + * @param optionsPreprocessor - callback to convert the response data to select options + */ +export function preloadExistingOptionsFactory( + fhirBaseUrl: string, + optionsPreprocessor: TransformOptions +) { + return async function preloadExistingOptions(values: string[]) { + const service = new FHIRServiceClass(fhirBaseUrl, ''); + const batchPayload = { + resourceType: 'Bundle', + type: 'batch', + entry: values.map((value) => { + return { + request: { + method: 'GET', + url: value, + }, + }; + }), + }; + return service + .customRequest({ + method: 'POST', + body: JSON.stringify(batchPayload), + url: fhirBaseUrl, + }) + .then((response) => { + return getResourcesFromBundle(response as IBundle).map( + optionsPreprocessor + ) as SelectOption[]; + }) + .catch(() => { + return [] as SelectOption[]; + }); + }; +} + +/** + * generate a select option from a practitioner resource + * + * @param obj - practitioner resource + */ +export const processPractitionerOption = (obj: IPractitioner) => { + return { + value: `${obj.resourceType}/${obj.id}`, + label: getPatientName(obj), + ref: obj, + } as SelectOption; +}; + +/** + * generate a select option from an organization resource + * + * @param obj - organization resource + */ +export const processOrganizationOption = (obj: IOrganization) => { + return { + value: `${obj.resourceType}/${obj.id}`, + label: obj.name, + ref: obj, + } as SelectOption; +}; diff --git a/packages/react-utils/src/components/AsyncSelect/PaginatedAsyncSelect/index.tsx b/packages/react-utils/src/components/AsyncSelect/PaginatedAsyncSelect/index.tsx index 5938e11a1..44cb99346 100644 --- a/packages/react-utils/src/components/AsyncSelect/PaginatedAsyncSelect/index.tsx +++ b/packages/react-utils/src/components/AsyncSelect/PaginatedAsyncSelect/index.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { URLParams } from '@opensrp/server-service'; import { IBundle } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IBundle'; -import { useInfiniteQuery } from 'react-query'; +import { useInfiniteQuery, useQuery } from 'react-query'; import { VerticalAlignBottomOutlined } from '@ant-design/icons'; import { Button, Divider, Select, Empty, Space, Spin, Alert } from 'antd'; import type { SelectProps } from 'antd'; @@ -34,6 +34,7 @@ export interface PaginatedAsyncSelectProps filterPageSize?: number; extraQueryParams?: URLParams; getFullOptionOnChange?: (obj: SelectOption | SelectOption[]) => void; + discoverUnknownOptions?: (values: string[]) => Promise[]>; } const debouncedFn = debounce((callback) => callback(), 500); @@ -59,6 +60,7 @@ export function PaginatedAsyncSelect( filterPageSize: pageSize = 20, extraQueryParams = {}, getFullOptionOnChange, + discoverUnknownOptions, ...restProps } = props; const defaultStartPage = 1; @@ -105,6 +107,53 @@ export function PaginatedAsyncSelect( refetchOnWindowFocus: false, }); + const options = ((data?.pages ?? []) as IBundle[]).flatMap((resourceBundle: IBundle) => { + const resources = getResourcesFromBundle(resourceBundle); + const allOptions = resources.map(transformOption); + const saneOptions = allOptions.filter((option) => option !== undefined); + return saneOptions as SelectOption[]; + }); + + const optionsByValue = options.reduce((acc, opt) => { + acc[opt.value] = opt; + return acc; + }, {} as Record>); + + const values = Array.isArray(props.value) ? props.value : [props.value]; + const defaultValues = Array.isArray(props.defaultValue) + ? props.defaultValue + : [props.defaultValue]; + const poolValuesToCheck = [...values, defaultValues]; + + const missingValues: string[] = []; + for (const value of poolValuesToCheck) { + if (typeof value === 'string') { + if (!(optionsByValue[value] as SelectOption | undefined)) { + missingValues.push(value); + } else { + // TODO - YAGNI - case when value is labelledValue + } + } + } + + const { data: preloadData, isLoading: preLoadDataIsLoading } = useQuery( + [missingValues], + () => props.discoverUnknownOptions?.(missingValues), + { + enabled: !!missingValues.length, + } + ); + + const preloadOptionsByValue = (preloadData ?? []).reduce((acc, option) => { + acc[option.value] = option; + return acc; + }, {} as Record>); + const fullSetOptions = { + ...preloadOptionsByValue, + ...optionsByValue, + }; + const updatedOptions = Object.values(fullSetOptions); + const changeHandler = ( value: string, fullOption: SelectOption | SelectOption[] @@ -118,13 +167,6 @@ export function PaginatedAsyncSelect( setSearchValue(value); }; - const options = ((data?.pages ?? []) as IBundle[]).flatMap((resourceBundle: IBundle) => { - const resources = getResourcesFromBundle(resourceBundle); - const allOptions = resources.map(transformOption); - const saneOptions = allOptions.filter((option) => option !== undefined); - return saneOptions as SelectOption[]; - }); - const pages = (data?.pages ?? []) as IBundle[]; const recordsFetchedNum = getTotalRecordsInBundles(pages); const totalPossibleRecords = getTotalRecordsOnApi(pages); @@ -135,10 +177,10 @@ export function PaginatedAsyncSelect( ...restProps, placeholder, onChange: changeHandler, - loading: isLoading, + loading: isLoading || preLoadDataIsLoading, notFoundContent: isLoading ? : , filterOption: false, - options: options, + options: updatedOptions, searchValue, dropdownRender: (menu: React.ReactNode) => ( <> diff --git a/packages/react-utils/src/helpers/dataLoaders.ts b/packages/react-utils/src/helpers/dataLoaders.ts index 8a5e2fd75..74a07f0ec 100644 --- a/packages/react-utils/src/helpers/dataLoaders.ts +++ b/packages/react-utils/src/helpers/dataLoaders.ts @@ -142,6 +142,16 @@ export class FHIRServiceClass { }); } + public async customRequest(requestOptions: fhirclient.RequestOptions) { + const accessToken = await OpenSRPService.processAcessToken(this.accessTokenOrCallBack); + const serve = FHIR.client(this.buildState(accessToken)); + return serve.request({ + signal: this.signal, + headers: this.headers, + ...requestOptions, + }); + } + public async delete(id: string) { const accessToken = await OpenSRPService.processAcessToken(this.accessTokenOrCallBack); const serve = FHIR.client(this.buildState(accessToken));