Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix load more org unit row errors #445

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 5 additions & 4 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2024-11-13T12:57:12.003Z\n"
"PO-Revision-Date: 2024-11-13T12:57:12.004Z\n"
"POT-Creation-Date: 2024-11-13T08:29:01.348Z\n"
"PO-Revision-Date: 2024-11-13T08:29:01.348Z\n"
"POT-Creation-Date: 2024-11-19T08:16:32.043Z\n"
"PO-Revision-Date: 2024-11-19T08:16:32.043Z\n"

msgid "schemas"
msgstr "schemas"
Expand Down Expand Up @@ -1042,6 +1040,9 @@ msgstr ""
"Choose when, and for which organisation units this category option will be "
"available."

msgid "Form name should not exceed 230 characters"
msgstr "Form name should not exceed 230 characters"

msgid "End date should be after start date"
msgstr "End date should be after start date"

Expand Down
167 changes: 142 additions & 25 deletions src/pages/organisationUnits/List.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
rootOrgUnits = [testOrgUnit()] as Partial<OrganisationUnit>[],
otherOrgUnits = [] as Partial<OrganisationUnit>[],
userDataStore = defaultUserDataStoreData,
customData = undefined,
}) => {
const routeOptions = {
handle: { section: SECTIONS_MAP.organisationUnit },
Expand All @@ -54,31 +55,33 @@
const result = render(
<TestComponentWithRouter
path="/organisationUnits"
customData={{
organisationUnits: (type: any, params: any) => {
if (type === 'read' && params.id !== undefined) {
const foundOrgUnit = organisationUnits.find(
(ou) => ou.id === params.id
)
return foundOrgUnit
}
if (type === 'read') {
const regex = /:(\w+)$/
const orgUnitFilter =
params.params.filter[0].match(regex)[1]
const filteredOrgUnits = organisationUnits.filter(
(ou) =>
ou.id === orgUnitFilter ||
ou.parent?.id === orgUnitFilter
)
return { organisationUnits: filteredOrgUnits }
}
if (type === 'delete') {
deleteOrgUnitMock(params)
}
},
userDataStore,
}}
customData={
customData || {
organisationUnits: (type: any, params: any) => {
if (type === 'read' && params.id !== undefined) {
const foundOrgUnit = organisationUnits.find(
(ou) => ou.id === params.id
)
return foundOrgUnit
}
if (type === 'read') {
const regex = /:(\w+)$/
const orgUnitFilter =
params.params.filter[0].match(regex)[1]
const filteredOrgUnits = organisationUnits.filter(
(ou) =>
ou.id === orgUnitFilter ||
ou.parent?.id === orgUnitFilter
)
return { organisationUnits: filteredOrgUnits }
}
if (type === 'delete') {
deleteOrgUnitMock(params)
}
},
userDataStore,
}
}
routeOptions={routeOptions}
>
<OrgUnitsList />
Expand Down Expand Up @@ -312,6 +315,120 @@
)
})

it('should load more rows on load more', async () => {
const rootOrg1 = testOrgUnit({ level: 1, childCount: 3 })
const root1Level2Child1 = testOrgUnit({
level: 2,
ancestors: [rootOrg1],
parentId: rootOrg1.id,
childCount: 0,
})
const root1Level2Child2 = testOrgUnit({
level: 2,
ancestors: [rootOrg1],
parentId: rootOrg1.id,
childCount: 0,
})
const root1Level2Child3 = testOrgUnit({
level: 2,
ancestors: [rootOrg1],
parentId: rootOrg1.id,
childCount: 0,
})

const customData = {
organisationUnits: (type: any, params: any) => {
if (type === 'read' && params.params.page === 1) {
return { organisationUnits: [rootOrg1, root1Level2Child1] }
}
if (type === 'read' && params.params.page === 2) {
return {
organisationUnits: [
rootOrg1,
root1Level2Child2,
root1Level2Child3,
],
}
}
},
userDataStore: defaultUserDataStoreData,
}

const screen = await renderList({
rootOrgUnits: [rootOrg1],
customData,

Check failure on line 359 in src/pages/organisationUnits/List.spec.tsx

View workflow job for this annotation

GitHub Actions / build

Type '{ organisationUnits: (type: any, params: any) => { organisationUnits: Partial<OrganisationUnit>[]; } | undefined; userDataStore: () => Promise<never>; }' is not assignable to type 'undefined'.
})

const tableRows = screen.getAllByTestId('dhis2-uicore-datatablerow')
expect(tableRows.length).toBe(4)
expect(tableRows[3]).toHaveTextContent(
`Load more for ${rootOrg1.displayName}`
)
fireEvent.click(within(tableRows[3]).getByTestId('load-more'))
let tableRowsRefreshed: any = []
await waitFor(() => {
tableRowsRefreshed = screen.getAllByTestId(
'dhis2-uicore-datatablerow'
)
expect(tableRowsRefreshed.length).toBe(5)
})
})

it('should load more the right rows when first load more fails', async () => {
const rootOrg1 = testOrgUnit({ level: 1, childCount: 3 })
const root1Level2Child1 = testOrgUnit({
level: 2,
ancestors: [rootOrg1],
parentId: rootOrg1.id,
childCount: 0,
})
const root1Level2Child2 = testOrgUnit({
level: 2,
ancestors: [rootOrg1],
parentId: rootOrg1.id,
childCount: 0,
})
const root1Level2Child3 = testOrgUnit({
level: 2,
ancestors: [rootOrg1],
parentId: rootOrg1.id,
childCount: 0,
})
const loadPageThreeMock = jest.fn()

const customData = {
organisationUnits: (type: any, params: any) => {
if (type === 'read' && params.params.page === 1) {
return { organisationUnits: [rootOrg1, root1Level2Child1] }
}
if (type === 'read' && params.params.page === 2) {
throw 'ERROR'
}
if (type === 'read' && params.params.page === 3) {
loadPageThreeMock()
}
},
userDataStore: defaultUserDataStoreData,
}

const screen = await renderList({
rootOrgUnits: [rootOrg1],
customData,

Check failure on line 416 in src/pages/organisationUnits/List.spec.tsx

View workflow job for this annotation

GitHub Actions / build

Type '{ organisationUnits: (type: any, params: any) => { organisationUnits: Partial<OrganisationUnit>[]; } | undefined; userDataStore: () => Promise<never>; }' is not assignable to type 'undefined'.
})

const tableRows = screen.getAllByTestId('dhis2-uicore-datatablerow')
expect(tableRows.length).toBe(4)
expect(tableRows[3]).toHaveTextContent(
`Load more for ${rootOrg1.displayName}`
)
fireEvent.click(within(tableRows[3]).getByTestId('load-more'))
await waitFor(() => {
expect(screen.getByTestId('dhis2-uicore-noticebox')).toBeVisible()
})
fireEvent.click(within(tableRows[3]).getByTestId('load-more'))
expect(loadPageThreeMock).not.toHaveBeenCalled()
})

it('should show a clickable delete action when org unit can be deleted', async () => {
const rootOrg = testOrgUnit({
level: 1,
Expand Down
1 change: 1 addition & 0 deletions src/pages/organisationUnits/list/OrganisationUnitRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export const OrganisationUnitRow = ({
colSpan="100"
style={{ textAlign: 'center' }}
onClick={() => fetchNextPage(parentRow.original.id)}
dataTest="load-more"
>
{i18n.t('Load more for {{orgUnitName}}', {
orgUnitName: parentRow.original.displayName,
Expand Down
67 changes: 39 additions & 28 deletions src/pages/organisationUnits/list/useOrganisationUnits.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useQuery, useQueries } from 'react-query'
import { useBoundResourceQueryFn } from '../../../lib/query/useBoundQueryFn'
import { OrganisationUnit, PagedResponse } from '../../../types/generated'
Expand Down Expand Up @@ -83,7 +83,7 @@ export const usePaginatedChildrenOrgUnitsController = (
const [parentIdPages, setFetchPages] = useState<ParentIdToPages>(
Object.fromEntries(Object.keys(parentIds).map((id) => [id, [1]]))
)

const [hasErrored, setHasErrored] = useState(false)
// this will create a query for each parent id and each page
// eg if parentIds = ['a', 'b'] and fetchPages = {a: [1, 2], b: [1]}
// then queries will be [['a', 1], ['a', 2], ['b', 1]]
Expand All @@ -98,40 +98,51 @@ export const usePaginatedChildrenOrgUnitsController = (
(id: string) => {
setFetchPages((prev) => {
const pages = prev[id] || [1]
const nextPaged = hasErrored
? [...pages]
: [...pages, pages[pages.length - 1] + 1]
return {
...prev,
[id]: [...pages, pages[pages.length - 1] + 1],
[id]: nextPaged,
}
})
},
[setFetchPages]
[setFetchPages, hasErrored]
)
const queryObjects = useMemo(
() =>
flatParentIdPages.map(([id, page]) => {
const resourceQuery = {
resource: 'organisationUnits',
params: {
fields: getOrgUnitFieldFilters(options.fieldFilters),
// `id:eq:id` is for an edge-case where a root-unit is a leaf-node
// and `parent.id`-filter would return empty results
filter: [`parent.id:eq:${id}`, `id:eq:${id}`],
rootJunction: 'OR',
order: 'displayName:asc',
page: page,
},
}
const queryOptions = {
enabled: options.enabled,
queryKey: [resourceQuery],
queryFn: boundQueryFn<OrganisationUnitResponse>,
staleTime: 60000,
cacheTime: 60000,
meta: { parent: id },
} as const
return queryOptions
}),
[flatParentIdPages]
)

const queryObjects = flatParentIdPages.map(([id, page]) => {
const resourceQuery = {
resource: 'organisationUnits',
params: {
fields: getOrgUnitFieldFilters(options.fieldFilters),
// `id:eq:id` is for an edge-case where a root-unit is a leaf-node
// and `parent.id`-filter would return empty results
filter: [`parent.id:eq:${id}`, `id:eq:${id}`],
rootJunction: 'OR',
order: 'displayName:asc',
page: page,
},
}
const queryOptions = {
enabled: options.enabled,
queryKey: [resourceQuery],
queryFn: boundQueryFn<OrganisationUnitResponse>,
staleTime: 60000,
cacheTime: 60000,
meta: { parent: id },
} as const
return queryOptions
})

const queries = useQueries(queryObjects)

useEffect(() => {
setHasErrored(queries.some((query) => query.isError))
}, [queries.some((query) => query.isError)])

return {
queries,
fetchNextPage,
Expand Down
Loading