From f286645dc2368ee31ed677492518791c3f373668 Mon Sep 17 00:00:00 2001 From: Macharia Muguku Date: Mon, 28 Jun 2021 14:19:32 +0300 Subject: [PATCH 1/8] add practitioner count endpoint --- packages/team-management/src/constants.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/team-management/src/constants.tsx b/packages/team-management/src/constants.tsx index f58076a74..b764e241b 100644 --- a/packages/team-management/src/constants.tsx +++ b/packages/team-management/src/constants.tsx @@ -5,7 +5,8 @@ export const URL_EDIT_TEAM = 'teams/edit/'; // // Practitioner // -export const PRACTITIONER_GET = 'practitioner/'; +export const PRACTITIONER_GET = 'practitioner'; +export const PRACTITIONER_COUNT = `${PRACTITIONER_GET}/count/`; export const PRACTITIONER_ROLE = 'practitionerRole'; export const PRACTITIONER_POST = `${PRACTITIONER_ROLE}/add/`; export const PRACTITIONER_DEL = `${PRACTITIONER_ROLE}/deleteByPractitioner`; From fc21fc791c1429f055d4aba513486fd888868fb1 Mon Sep 17 00:00:00 2001 From: Macharia Muguku Date: Mon, 28 Jun 2021 14:21:30 +0300 Subject: [PATCH 2/8] fetch all practitioners recursively from a paginated end point --- .../src/components/TeamsAddEdit/index.tsx | 73 ++++++++++++++++++- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/packages/team-management/src/components/TeamsAddEdit/index.tsx b/packages/team-management/src/components/TeamsAddEdit/index.tsx index b896a97ec..15a4747f8 100644 --- a/packages/team-management/src/components/TeamsAddEdit/index.tsx +++ b/packages/team-management/src/components/TeamsAddEdit/index.tsx @@ -9,6 +9,7 @@ import { TEAMS_GET, TEAM_PRACTITIONERS, PRACTITIONER_ROLE, + PRACTITIONER_COUNT, } from '../../constants'; import { OpenSRPService } from '@opensrp/react-utils'; import { sendErrorNotification } from '@opensrp/notifications'; @@ -90,6 +91,73 @@ export const defaultProps = { opensrpBaseURL: '', }; +/** + * function to fetch a paginated practitioner object + * + * @param opensrpBaseURL - OpenSRP API base URL + * @param practitionersEndpoint - OpenSRP practitioners endpoint + * @param pageNumber - paginated page number + * @param pageSize - paginated page size + * @param errorNotification - the error message to show for a failed request + * @returns {Promise} an array of practitioners + */ +async function fetchPractitioners( + opensrpBaseURL: string, + practitionersEndpoint: string, + pageNumber: number, + pageSize: number, + errorNotification: string = lang.ERROR_OCCURRED +): Promise { + const composePaginatedEndpoint = `${practitionersEndpoint}?pageNumber=${pageNumber}&pageSize=${pageSize}`; + const serve = new OpenSRPService(composePaginatedEndpoint, opensrpBaseURL); + try { + const practitioners: Practitioner[] = await serve.list(); + return practitioners; + } catch (_) { + sendErrorNotification(errorNotification); + return []; + } +} + +/** + * function to fetch paginated practitioners resource recursively + * + * @param opensrpBaseURL - OpenSRP API base URL + * @param practitionersCountEndpoint - OpenSRP practitioners count endpoint + * @param practitionersEndpoint - OpenSRP practitioners endpoint + * @returns {Promise} - an array of all practitioners in a paginated endpoint + */ +async function fetchPractitionersRecursively( + opensrpBaseURL: string, + practitionersCountEndpoint: string, + practitionersEndpoint: string +): Promise { + // practitioner array to fill recursively + const allPractitioners: Practitioner[] = []; + + // get the total number of practitioners + const serve = new OpenSRPService(practitionersCountEndpoint, opensrpBaseURL); + const practitionerCount: number = await serve.list(); + + const pageSize = 1000; + + // get the maximum possible page numbers + const maxPageNo = Math.ceil(practitionerCount / pageSize); + + // add practitioners recursively based on page number + for (let pageNumber = 1; pageNumber <= maxPageNo; pageNumber++) { + const practitioners = await fetchPractitioners( + opensrpBaseURL, + practitionersEndpoint, + pageNumber, + pageSize + ); + allPractitioners.push(...practitioners); + } + + return allPractitioners; +} + export const TeamsAddEdit: React.FC = (props: Props) => { const params: { id: string } = useParams(); const [initialValue, setInitialValue] = useState(null); @@ -124,9 +192,8 @@ export const TeamsAddEdit: React.FC = (props: Props) => { */ useEffect(() => { if ((disableTeamMemberReassignment && practitionersRole) || !disableTeamMemberReassignment) { - const serve = new OpenSRPService(PRACTITIONER_GET, opensrpBaseURL); - serve - .list() + // fetch practitioners recursively from a paginated endpoint + fetchPractitionersRecursively(opensrpBaseURL, PRACTITIONER_COUNT, PRACTITIONER_GET) .then((response: Practitioner[]) => { // filter out inactive practitioners const activePractitioners = response.filter((practitioner) => practitioner.active); From 4bafcfa0faa478fa006e657f5c50dad0d82cffc4 Mon Sep 17 00:00:00 2001 From: Macharia Muguku Date: Mon, 28 Jun 2021 15:48:24 +0300 Subject: [PATCH 3/8] use a dummy opensrp url, add mock for practitioner count endpoint, use custom props on all components, update fetch mock calls for practitioner count endpoint and pagination, update snapshots --- .../components/TeamsAddEdit/tests/fixtures.ts | 2 +- .../TeamsAddEdit/tests/index.test.tsx | 88 +++++++++++++++---- 2 files changed, 73 insertions(+), 17 deletions(-) diff --git a/packages/team-management/src/components/TeamsAddEdit/tests/fixtures.ts b/packages/team-management/src/components/TeamsAddEdit/tests/fixtures.ts index fb034df8b..7a6768787 100644 --- a/packages/team-management/src/components/TeamsAddEdit/tests/fixtures.ts +++ b/packages/team-management/src/components/TeamsAddEdit/tests/fixtures.ts @@ -4,7 +4,7 @@ import { Practitioner, PractitionerPOST } from '../../../ducks/practitioners'; import { FormField } from '../Form'; export const accessToken = 'token'; -export const opensrpBaseURL = 'https://opensrp-stage.smartregister.org/opensrp/rest/'; +export const opensrpBaseURL = 'https://some.opensrp.url/'; export const id = '258b4dec-79d3-546d-9c5c-f172aa7e03b0'; export const team: Organization = { diff --git a/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx b/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx index 894cad056..ac953c0c0 100644 --- a/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx +++ b/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx @@ -40,6 +40,8 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { }); it('renders without crashing', async () => { + // practitioner count endpoint - pageSize === 1k. so if resp < 1k, pageNumber = 1 + fetch.mockResponseOnce(JSON.stringify(900)); fetch.mockResponseOnce(JSON.stringify(practitioners)); const wrapper = mount( @@ -57,7 +59,18 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { expect(fetch.mock.calls).toMatchObject([ [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitioner/', + 'https://some.opensrp.url/practitioner/count/', + { + headers: { + accept: 'application/json', + authorization: 'Bearer hunter2', + 'content-type': 'application/json;charset=UTF-8', + }, + method: 'GET', + }, + ], + [ + 'https://some.opensrp.url/practitioner?pageNumber=1&pageSize=1000', { headers: { accept: 'application/json', @@ -76,12 +89,14 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { it('renders with id without crashing', async () => { fetch.mockResponseOnce(JSON.stringify(practitioners)); fetch.mockResponseOnce(JSON.stringify(team)); + // practitioner count endpoint - pageSize === 1k. so if resp < 1k, pageNumber = 1 + fetch.mockResponseOnce(JSON.stringify(900)); fetch.mockResponseOnce(JSON.stringify(practitioners)); const wrapper = mount( - + } /> ); @@ -93,7 +108,7 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { expect(fetch.mock.calls).toMatchObject([ [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/organization/practitioner/258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/organization/practitioner/258b4dec-79d3-546d-9c5c-f172aa7e03b0', { headers: { accept: 'application/json', @@ -104,7 +119,7 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/organization/258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/organization/258b4dec-79d3-546d-9c5c-f172aa7e03b0', { headers: { accept: 'application/json', @@ -115,7 +130,18 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitioner/', + 'https://some.opensrp.url/practitioner/count/', + { + headers: { + accept: 'application/json', + authorization: 'Bearer hunter2', + 'content-type': 'application/json;charset=UTF-8', + }, + method: 'GET', + }, + ], + [ + 'https://some.opensrp.url/practitioner?pageNumber=1&pageSize=1000', { headers: { accept: 'application/json', @@ -138,7 +164,7 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { mount( - + } /> ); @@ -189,12 +215,14 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { it('render with correct team name in header', async () => { fetch.mockResponseOnce(JSON.stringify(practitioners)); fetch.mockResponseOnce(JSON.stringify(team)); + // practitioner count endpoint - pageSize === 1k. so if resp < 1k, pageNumber = 1 + fetch.mockResponseOnce(JSON.stringify(900)); fetch.mockResponseOnce(JSON.stringify(practitioners)); const wrapper = mount( - + } /> ); @@ -206,7 +234,7 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { expect(fetch.mock.calls).toMatchObject([ [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/organization/practitioner/258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/organization/practitioner/258b4dec-79d3-546d-9c5c-f172aa7e03b0', { headers: { accept: 'application/json', @@ -217,7 +245,7 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/organization/258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/organization/258b4dec-79d3-546d-9c5c-f172aa7e03b0', { headers: { accept: 'application/json', @@ -228,7 +256,18 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitioner/', + 'https://some.opensrp.url/practitioner/count/', + { + headers: { + accept: 'application/json', + authorization: 'Bearer hunter2', + 'content-type': 'application/json;charset=UTF-8', + }, + method: 'GET', + }, + ], + [ + 'https://some.opensrp.url/practitioner?pageNumber=1&pageSize=1000', { headers: { accept: 'application/json', @@ -251,12 +290,14 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { it('correctly adds/removes members from team', async () => { fetch.mockResponseOnce(JSON.stringify(practitioners)); fetch.mockResponseOnce(JSON.stringify(team)); + // practitioner count endpoint - pageSize === 1k. so if resp < 1k, pageNumber = 1 + fetch.mockResponseOnce(JSON.stringify(900)); fetch.mockResponseOnce(JSON.stringify(practitioners)); const wrapper = mount( - + } /> ); @@ -267,9 +308,20 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { wrapper.update(); - expect(fetch.mock.calls).toEqual([ + expect(fetch.mock.calls).toMatchObject([ + [ + 'https://some.opensrp.url/organization/practitioner/258b4dec-79d3-546d-9c5c-f172aa7e03b0', + { + headers: { + accept: 'application/json', + authorization: 'Bearer hunter2', + 'content-type': 'application/json;charset=UTF-8', + }, + method: 'GET', + }, + ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/organization/practitioner/258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/organization/258b4dec-79d3-546d-9c5c-f172aa7e03b0', { headers: { accept: 'application/json', @@ -280,7 +332,7 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/organization/258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/practitioner/count/', { headers: { accept: 'application/json', @@ -291,7 +343,7 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitioner/', + 'https://some.opensrp.url/practitioner?pageNumber=1&pageSize=1000', { headers: { accept: 'application/json', @@ -323,12 +375,14 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { it('renders with inactive practitioners filtered out', async () => { fetch.mockResponseOnce(JSON.stringify(practitioners)); fetch.mockResponseOnce(JSON.stringify(team)); + // practitioner count endpoint - pageSize === 1k. so if resp < 1k, pageNumber = 1 + fetch.mockResponseOnce(JSON.stringify(900)); fetch.mockResponseOnce(JSON.stringify(practitioners)); const wrapper = mount( - + } /> ); @@ -369,6 +423,8 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { fetch.mockResponseOnce(JSON.stringify(practitioners.slice(4))); // subset of all practitioners - from index 4 to end of arr fetch.mockResponseOnce(JSON.stringify(team)); fetch.mockResponseOnce(JSON.stringify(practitionerRole)); // with practitioner id matching the active subset's identifier id + // practitioner count endpoint - pageSize === 1k. so if resp < 1k, pageNumber = 1 + fetch.mockResponseOnce(JSON.stringify(900)); fetch.mockResponseOnce(JSON.stringify(practitioners)); // enable filtering out configuration From 954a982758fe0a31d87b1345e05246e1a4285971 Mon Sep 17 00:00:00 2001 From: Macharia Muguku Date: Mon, 28 Jun 2021 15:52:17 +0300 Subject: [PATCH 4/8] add test for fetching practitioners recursively from a paginated endpoint --- .../TeamsAddEdit/tests/index.test.tsx | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx b/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx index ac953c0c0..dc7bc1c4f 100644 --- a/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx +++ b/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx @@ -484,4 +484,72 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { ] `); }); + it('fetches all practitioners recursively from a paginated endpoint', async () => { + // practitioners that are already members of the team + fetch.mockResponseOnce(JSON.stringify(practitioners.slice(6))); + // team details + fetch.mockResponseOnce(JSON.stringify(team)); + // practitioner count endpoint - pageSize === 1k. so if 1k < resp < 2k, pageNumber = 2 + fetch.mockResponseOnce(JSON.stringify(1450)); + // expect two practitioner calls, one for each page + fetch + // split fixture object between the requests + .mockOnce(JSON.stringify(practitioners.slice(0, 3))) + .mockOnce(JSON.stringify(practitioners.slice(3))); + + // editing team with id === ${id} + const wrapper = mount( + + + } /> + + + ); + + await act(async () => { + await flushPromises(); + wrapper.update(); + }); + + // compose request object with request url only + const composeRequests = fetch.mock.calls.map((req) => req[0]); + + // expect all calls: + // get team members, team details, practitioner count, and two practitioner pages + expect(composeRequests).toMatchObject([ + 'https://some.opensrp.url/organization/practitioner/258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/organization/258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/practitioner/count/', + 'https://some.opensrp.url/practitioner?pageNumber=1&pageSize=1000', + 'https://some.opensrp.url/practitioner?pageNumber=2&pageSize=1000', + ]); + + // find antd Select with id 'practitioners' in the 'Form' component + const practitionersSelect = wrapper.find('Form').find('Select#practitioners'); + + // simulate click on select - to show dropdown items + practitionersSelect.find('.ant-select-selector').simulate('mousedown'); + + await act(async () => { + await flushPromises(); + wrapper.update(); + }); + + // re-find + const practitionersSelect2 = wrapper.find('Form').find('Select#practitioners'); + // find antd select options + const selectOptions = practitionersSelect2.find('.ant-select-item-option-content'); + + // remove inactive users and only return the names + const filteredPractitioners = practitioners + .filter((practitioner) => practitioner.active) + .map((practitioner) => practitioner.name); + + // expect antd select option text to equal all practitioners (except those who are inactive) + expect(selectOptions.map((opt) => opt.text())).toStrictEqual(filteredPractitioners); + + wrapper.unmount(); + }); }); From 195a2849e9aa2cee32c32b6ceb38d51fc7fa82fc Mon Sep 17 00:00:00 2001 From: Macharia Muguku Date: Mon, 28 Jun 2021 16:36:03 +0300 Subject: [PATCH 5/8] update snapshots for dummy opensrp url --- .../TeamsAddEdit/tests/Form.test.tsx | 24 +++++++++---------- .../tests/__snapshots__/table.test.tsx.snap | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/team-management/src/components/TeamsAddEdit/tests/Form.test.tsx b/packages/team-management/src/components/TeamsAddEdit/tests/Form.test.tsx index 793f2d70c..a9c29cd16 100644 --- a/packages/team-management/src/components/TeamsAddEdit/tests/Form.test.tsx +++ b/packages/team-management/src/components/TeamsAddEdit/tests/Form.test.tsx @@ -217,7 +217,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { expect(fetch.mock.calls).toMatchObject([ [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/organization', + 'https://some.opensrp.url/organization', { 'Cache-Control': 'no-cache', Pragma: 'no-cache', @@ -245,7 +245,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitionerRole/deleteByPractitioner?practitioner=0&organization=b0c20f20-c1c0-4ea3-b855-4fcb23f6ae2a', + 'https://some.opensrp.url/practitionerRole/deleteByPractitioner?practitioner=0&organization=b0c20f20-c1c0-4ea3-b855-4fcb23f6ae2a', { headers: { accept: 'application/json', @@ -256,7 +256,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitionerRole/deleteByPractitioner?practitioner=1&organization=b0c20f20-c1c0-4ea3-b855-4fcb23f6ae2a', + 'https://some.opensrp.url/practitionerRole/deleteByPractitioner?practitioner=1&organization=b0c20f20-c1c0-4ea3-b855-4fcb23f6ae2a', { headers: { accept: 'application/json', @@ -267,7 +267,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitionerRole/deleteByPractitioner?practitioner=2&organization=b0c20f20-c1c0-4ea3-b855-4fcb23f6ae2a', + 'https://some.opensrp.url/practitionerRole/deleteByPractitioner?practitioner=2&organization=b0c20f20-c1c0-4ea3-b855-4fcb23f6ae2a', { headers: { accept: 'application/json', @@ -301,7 +301,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { expect(fetch.mock.calls).toMatchObject([ [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/organization/258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/organization/258b4dec-79d3-546d-9c5c-f172aa7e03b0', { 'Cache-Control': 'no-cache', Pragma: 'no-cache', @@ -329,7 +329,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitionerRole/deleteByPractitioner?practitioner=0&organization=258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/practitionerRole/deleteByPractitioner?practitioner=0&organization=258b4dec-79d3-546d-9c5c-f172aa7e03b0', { headers: { accept: 'application/json', @@ -340,7 +340,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitionerRole/deleteByPractitioner?practitioner=1&organization=258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/practitionerRole/deleteByPractitioner?practitioner=1&organization=258b4dec-79d3-546d-9c5c-f172aa7e03b0', { headers: { accept: 'application/json', @@ -351,7 +351,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitionerRole/add/', + 'https://some.opensrp.url/practitionerRole/add/', { 'Cache-Control': 'no-cache', Pragma: 'no-cache', @@ -390,7 +390,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { expect(fetch.mock.calls).toMatchObject([ [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/organization/258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/organization/258b4dec-79d3-546d-9c5c-f172aa7e03b0', { 'Cache-Control': 'no-cache', Pragma: 'no-cache', @@ -418,7 +418,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitionerRole/deleteByPractitioner?practitioner=0&organization=258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/practitionerRole/deleteByPractitioner?practitioner=0&organization=258b4dec-79d3-546d-9c5c-f172aa7e03b0', { headers: { accept: 'application/json', @@ -429,7 +429,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitionerRole/deleteByPractitioner?practitioner=1&organization=258b4dec-79d3-546d-9c5c-f172aa7e03b0', + 'https://some.opensrp.url/practitionerRole/deleteByPractitioner?practitioner=1&organization=258b4dec-79d3-546d-9c5c-f172aa7e03b0', { headers: { accept: 'application/json', @@ -440,7 +440,7 @@ describe('Team-management/TeamsAddEdit/Form', () => { }, ], [ - 'https://opensrp-stage.smartregister.org/opensrp/rest/practitionerRole/add/', + 'https://some.opensrp.url/practitionerRole/add/', { 'Cache-Control': 'no-cache', Pragma: 'no-cache', diff --git a/packages/team-management/src/components/TeamsView/tests/__snapshots__/table.test.tsx.snap b/packages/team-management/src/components/TeamsView/tests/__snapshots__/table.test.tsx.snap index dd33a3033..296a7a010 100644 --- a/packages/team-management/src/components/TeamsView/tests/__snapshots__/table.test.tsx.snap +++ b/packages/team-management/src/components/TeamsView/tests/__snapshots__/table.test.tsx.snap @@ -34,7 +34,7 @@ exports[`components/TeamsView/table renders without crashing: Table 1`] = ` }, ] } - opensrpBaseURL="https://opensrp-stage.smartregister.org/opensrp/rest/" + opensrpBaseURL="https://some.opensrp.url/" setDetail={[Function]} setPractitionersList={[Function]} /> From aff169d3df31d951cd73715adc4604ec0f6bfbe2 Mon Sep 17 00:00:00 2001 From: Macharia Muguku Date: Mon, 28 Jun 2021 22:19:30 +0300 Subject: [PATCH 6/8] url params from object, pass pageSize as a function param, promise.all --- .../src/components/TeamsAddEdit/index.tsx | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/team-management/src/components/TeamsAddEdit/index.tsx b/packages/team-management/src/components/TeamsAddEdit/index.tsx index 15a4747f8..ff026db99 100644 --- a/packages/team-management/src/components/TeamsAddEdit/index.tsx +++ b/packages/team-management/src/components/TeamsAddEdit/index.tsx @@ -97,7 +97,7 @@ export const defaultProps = { * @param opensrpBaseURL - OpenSRP API base URL * @param practitionersEndpoint - OpenSRP practitioners endpoint * @param pageNumber - paginated page number - * @param pageSize - paginated page size + * @param pageSize - number of practitioners in each page * @param errorNotification - the error message to show for a failed request * @returns {Promise} an array of practitioners */ @@ -108,10 +108,13 @@ async function fetchPractitioners( pageSize: number, errorNotification: string = lang.ERROR_OCCURRED ): Promise { - const composePaginatedEndpoint = `${practitionersEndpoint}?pageNumber=${pageNumber}&pageSize=${pageSize}`; - const serve = new OpenSRPService(composePaginatedEndpoint, opensrpBaseURL); + const paginationParams = { + pageNumber, + pageSize, + }; + const serve = new OpenSRPService(practitionersEndpoint, opensrpBaseURL); try { - const practitioners: Practitioner[] = await serve.list(); + const practitioners: Practitioner[] = await serve.list(paginationParams); return practitioners; } catch (_) { sendErrorNotification(errorNotification); @@ -125,37 +128,38 @@ async function fetchPractitioners( * @param opensrpBaseURL - OpenSRP API base URL * @param practitionersCountEndpoint - OpenSRP practitioners count endpoint * @param practitionersEndpoint - OpenSRP practitioners endpoint + * @param pageSize - number of practitioners in each page * @returns {Promise} - an array of all practitioners in a paginated endpoint */ async function fetchPractitionersRecursively( opensrpBaseURL: string, practitionersCountEndpoint: string, - practitionersEndpoint: string + practitionersEndpoint: string, + pageSize = 1000 ): Promise { - // practitioner array to fill recursively - const allPractitioners: Practitioner[] = []; - // get the total number of practitioners const serve = new OpenSRPService(practitionersCountEndpoint, opensrpBaseURL); const practitionerCount: number = await serve.list(); - const pageSize = 1000; - // get the maximum possible page numbers const maxPageNo = Math.ceil(practitionerCount / pageSize); - // add practitioners recursively based on page number + // compose a promise array to resolve in parallel + const promises: Promise[] = []; for (let pageNumber = 1; pageNumber <= maxPageNo; pageNumber++) { - const practitioners = await fetchPractitioners( - opensrpBaseURL, - practitionersEndpoint, - pageNumber, - pageSize - ); - allPractitioners.push(...practitioners); + promises.push(fetchPractitioners(opensrpBaseURL, practitionersEndpoint, pageNumber, pageSize)); } - return allPractitioners; + // fetch practitioners recursively according to page numbers + return Promise.all(promises) + .then((practitioners: Practitioner[][]) => { + // flatten 2D array - [[][]] + const flatPractitionersArray = practitioners.flat(); + return flatPractitionersArray; + }) + .catch((err) => { + throw err; + }); } export const TeamsAddEdit: React.FC = (props: Props) => { From 8b692587fc9fbec8b781e755c925fb89ceb3cc5c Mon Sep 17 00:00:00 2001 From: Macharia Muguku Date: Mon, 28 Jun 2021 22:39:56 +0300 Subject: [PATCH 7/8] make pagination configurable --- app/.env.sample | 3 ++- app/src/App/App.tsx | 1 + app/src/App/utils.tsx | 4 +++- app/src/configs/env.ts | 2 ++ .../src/components/TeamsAddEdit/index.tsx | 14 ++++++++++---- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/.env.sample b/app/.env.sample index 5efc6c986..f614fce78 100644 --- a/app/.env.sample +++ b/app/.env.sample @@ -40,7 +40,8 @@ REACT_APP_PROJECT_LANGUAGE_CODE=eusm REACT_APP_SUPPORTED_LANGUAGES=en,fr REACT_APP_ENABLE_LANGUAGE_SWITCHER=true # user roles -REACT_APP_OPENSRP_ROLES={"USERS": "ROLE_EDIT_KEYCLOAK_USERS", "PLANS": "ROLE_VIEW_KEYCLOAK_USERS", "LOCATIONS": "ROLE_VIEW_KEYCLOAK_USERS", "CARD_SUPPORT": "ROLE_VIEW_KEYCLOAK_USERS", "INVENTORY": "ROLE_VIEW_KEYCLOAK_USERS", "TEAMS": "ROLE_VIEW_KEYCLOAK_USERS", "PRODUCT_CATALOGUE": "ROLE_VIEW_KEYCLOAK_USERS", "FORM_CONFIGURATION": "ROLE_VIEW_KEYCLOAK_USERS"} +REACT_APP_OPENSRP_ROLES="{"USERS": "ROLE_EDIT_KEYCLOAK_USERS", "PLANS": "ROLE_VIEW_KEYCLOAK_USERS", "LOCATIONS": "ROLE_VIEW_KEYCLOAK_USERS", "CARD_SUPPORT": "ROLE_VIEW_KEYCLOAK_USERS", "INVENTORY": "ROLE_VIEW_KEYCLOAK_USERS", "TEAMS": "ROLE_VIEW_KEYCLOAK_USERS", "PRODUCT_CATALOGUE": "ROLE_VIEW_KEYCLOAK_USERS", "FORM_CONFIGURATION": "ROLE_VIEW_KEYCLOAK_USERS"}" REACT_APP_DISABLE_TEAM_MEMBER_REASSIGNMENT=false REACT_APP_USER_FORM_HIDDEN_FIELDS="" REACT_APP_USER_FORM_RENDER_FIELDS="" +REACT_APP_PAGINATION_SIZE=1000 diff --git a/app/src/App/App.tsx b/app/src/App/App.tsx index 9fec04381..96d8acb11 100644 --- a/app/src/App/App.tsx +++ b/app/src/App/App.tsx @@ -543,6 +543,7 @@ const App: React.FC = () => { exact path={URL_TEAMS_ADD} component={TeamsAddEdit} + {...teamManagementProps} /> { // get the total number of practitioners const serve = new OpenSRPService(practitionersCountEndpoint, opensrpBaseURL); @@ -167,7 +168,7 @@ export const TeamsAddEdit: React.FC = (props: Props) => { const [initialValue, setInitialValue] = useState(null); const [practitioners, setPractitioners] = useState(null); const [practitionersRole, setPractitionersRole] = useState(null); - const { opensrpBaseURL, disableTeamMemberReassignment } = props; + const { opensrpBaseURL, disableTeamMemberReassignment, paginationSize } = props; useEffect(() => { if (params.id) setupInitialValue(params.id, opensrpBaseURL, setInitialValue); @@ -197,7 +198,12 @@ export const TeamsAddEdit: React.FC = (props: Props) => { useEffect(() => { if ((disableTeamMemberReassignment && practitionersRole) || !disableTeamMemberReassignment) { // fetch practitioners recursively from a paginated endpoint - fetchPractitionersRecursively(opensrpBaseURL, PRACTITIONER_COUNT, PRACTITIONER_GET) + fetchPractitionersRecursively( + opensrpBaseURL, + PRACTITIONER_COUNT, + PRACTITIONER_GET, + paginationSize + ) .then((response: Practitioner[]) => { // filter out inactive practitioners const activePractitioners = response.filter((practitioner) => practitioner.active); @@ -233,7 +239,7 @@ export const TeamsAddEdit: React.FC = (props: Props) => { }) .catch(() => sendErrorNotification(lang.ERROR_OCCURRED)); } - }, [disableTeamMemberReassignment, opensrpBaseURL, practitionersRole]); + }, [disableTeamMemberReassignment, opensrpBaseURL, paginationSize, practitionersRole]); if (!practitioners || (params.id && !initialValue)) return ; From f3ea5096102f66dbb56d2df74a092789bc6b37b6 Mon Sep 17 00:00:00 2001 From: Macharia Muguku Date: Mon, 28 Jun 2021 22:46:38 +0300 Subject: [PATCH 8/8] add pagination prop to tests --- .../src/components/TeamsAddEdit/tests/index.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx b/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx index dc7bc1c4f..b908ff411 100644 --- a/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx +++ b/packages/team-management/src/components/TeamsAddEdit/tests/index.test.tsx @@ -19,6 +19,7 @@ describe('Team-management/TeamsAddEdit/TeamsAddEdit', () => { const props = { opensrpBaseURL, disableTeamMemberReassignment: false, + paginationSize: 1000, }; beforeAll(() => { store.dispatch(