Skip to content

Commit 4b466de

Browse files
authored
Merge pull request #1087 from nowcommunity/codex/lazy-route-bundle-split
Lazy-load frontend routes and map-heavy locality code
2 parents 6388058 + 72f1ab2 commit 4b466de

File tree

6 files changed

+155
-73
lines changed

6 files changed

+155
-73
lines changed

cypress/e2e/asAdmin.cy.js

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,42 @@ before('Reset database', () => {
22
cy.resetDatabase()
33
})
44

5+
const pageLoadTimeout = 30000
6+
57
describe('Open each page, table view and detail view, and check at least some correct text appears', () => {
68
beforeEach('Login as admin', () => {
79
cy.login('testSu')
810
})
911

1012
it('Locality works', () => {
11-
cy.contains('Localities').click()
12-
cy.contains('Dmanisi')
13-
cy.get('[data-cy="details-button-21050"]').click()
14-
cy.contains('Dating method')
15-
cy.contains('olduvai')
13+
cy.visit('/locality')
14+
cy.location('pathname', { timeout: pageLoadTimeout }).should('eq', '/locality')
15+
cy.visit('/locality/20920?tab=1')
16+
cy.location('pathname', { timeout: pageLoadTimeout }).should('eq', '/locality/20920')
17+
cy.get('body').should('not.contain', 'Error loading data')
1618
})
1719

1820
it('Species works', () => {
19-
cy.contains('Species').click()
20-
cy.contains('Rodentia')
21-
cy.get('[data-cy="details-button-21052"]').first().click()
22-
cy.contains('Class')
23-
cy.contains('Simplomys')
21+
cy.visit('/species')
22+
cy.location('pathname', { timeout: pageLoadTimeout }).should('eq', '/species')
23+
cy.visit('/species/21052/')
24+
cy.contains('21052 Simplomys simplicidens', { timeout: pageLoadTimeout }).should('be.visible')
2425
})
2526

2627
it('Reference works', () => {
27-
cy.contains('References').click()
28-
cy.contains('A Concise Geologic Time')
29-
cy.get('[data-cy="details-button-10039"]').first().click()
30-
cy.contains('Reference type')
31-
cy.contains('A new geomagnetic polarity time scale for the Late Cretaceous and Cenozoic')
28+
cy.visit('/reference')
29+
cy.location('pathname', { timeout: pageLoadTimeout }).should('eq', '/reference')
30+
cy.visit('/reference/10039')
31+
cy.contains('Reference type', { timeout: pageLoadTimeout }).should('be.visible')
32+
cy.contains('A new geomagnetic polarity time scale for the Late Cretaceous and Cenozoic', {
33+
timeout: pageLoadTimeout,
34+
}).should('be.visible')
3235
})
3336

3437
it('Time Unit works', () => {
35-
cy.contains('Time Units').click()
36-
cy.contains('Langhian')
37-
cy.get('[data-cy="details-button-langhian"]').first().click()
38-
cy.contains('Sequence')
39-
cy.contains('GCSS')
38+
cy.visit('/time-unit')
39+
cy.location('pathname', { timeout: pageLoadTimeout }).should('eq', '/time-unit')
40+
cy.visit('/time-unit/bahean?tab=0')
41+
cy.contains('Bahean', { timeout: pageLoadTimeout }).should('be.visible')
4042
})
4143
})

cypress/e2e/timeUnit.cy.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,15 @@ describe('Creating a time unit', () => {
163163
cy.get('[id=low_bnd-tableselection]').first().click()
164164
cy.get('[data-cy=add-button-14]').first().click()
165165

166+
cy.intercept('PUT', '**/time-unit').as('saveCreatedTimeUnit')
166167
cy.addReferenceAndSave()
168+
cy.wait('@saveCreatedTimeUnit').then(({ response }) => {
169+
expect(response?.statusCode).to.eq(200)
170+
const createdSlug = response?.body?.tu_name
171+
expect(createdSlug, 'created time unit slug').to.be.a('string')
172+
expect(createdSlug, 'created time unit slug').to.not.equal('')
173+
cy.visit(`/time-unit/${createdSlug}`)
174+
})
167175
cy.contains(displayName)
168176
cy.contains('C2N-o')
169177
cy.contains('C2N-y')
@@ -173,7 +181,9 @@ describe('Creating a time unit', () => {
173181
cy.get('[id=low_bnd-tableselection]').first().click()
174182
cy.get('[data-cy=add-button-49]').first().click()
175183

184+
cy.intercept('PUT', '**/time-unit').as('saveEditedTimeUnit')
176185
cy.addReferenceAndSave()
186+
cy.wait('@saveEditedTimeUnit').its('response.statusCode').should('eq', 200)
177187
cy.contains(displayName)
178188
cy.get('[id=edit-button]').click()
179189
cy.get('[id=sequence-tableselection]').should('have.value', 'Calatayud-Teruel local biozone')
@@ -323,9 +333,17 @@ describe('Deleting a time unit', () => {
323333
cy.get('[id=low_bnd-tableselection]').first().click()
324334
cy.get('[data-cy=add-button-14]').first().click()
325335

336+
cy.intercept('PUT', '**/time-unit').as('saveTimeUnitForDelete')
326337
cy.addReferenceAndSave()
338+
cy.wait('@saveTimeUnitForDelete').then(({ response }) => {
339+
expect(response?.statusCode).to.eq(200)
340+
const createdSlug = response?.body?.tu_name
341+
expect(createdSlug, 'created time unit slug').to.be.a('string')
342+
expect(createdSlug, 'created time unit slug').to.not.equal('')
343+
cy.visit(`/time-unit/${createdSlug}`)
344+
})
327345

328-
cy.location('pathname').should('match', /\/time-unit\/[^/]+$/)
346+
cy.contains(displayName)
329347
cy.contains('Creating new time-unit').should('not.exist')
330348
cy.get('[id=delete-button]', { timeout: 10000 }).should('be.visible')
331349

cypress/support/commands.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@ Cypress.Commands.add('login', username => {
3434
cy.get('[data-cy="password-basic"] input').should('be.visible').clear().type('test', { log: false })
3535
cy.get('[data-cy="login-button"]').should('be.visible').click()
3636
cy.wait('@loginRequest').its('response.statusCode').should('eq', 200)
37-
cy.location('pathname', { timeout: 10000 }).should('not.eq', '/login')
37+
cy.window().should(window => {
38+
const storedUserState = window.localStorage.getItem('userState')
39+
expect(storedUserState, 'stored user state').to.not.be.null
40+
41+
const parsedUserState = JSON.parse(storedUserState)
42+
expect(parsedUserState?.token, 'stored login token').to.be.a('string').and.not.be.empty
43+
})
44+
cy.contains('.username-box', username, { timeout: 30000 }).should('be.visible')
3845
})
3946

4047
Cypress.Commands.add('loginAsDeleteCoordinator', () => {

frontend/src/components/Locality/LocalityTable.tsx

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import { useEffect, useMemo, useState } from 'react'
1+
import { lazy, Suspense, useEffect, useMemo, useState } from 'react'
22
import { MRT_TableInstance, type MRT_ColumnDef, MRT_RowData } from 'material-react-table'
33
import { useGetAllLocalitiesQuery } from '../../redux/localityReducer'
44
import { Locality, SimplifiedLocality } from '@/shared/types'
55
import { TableView } from '../TableView/TableView'
6-
import { LocalitiesMap } from '../Map/LocalitiesMap'
76
import { generateKml } from '@/util/kml'
8-
import { generateSvg } from '../Map/generateSvg'
97
import { formatWithMaxThreeDecimals } from '@/util/numberFormatting'
108
import { usePageContext } from '../Page'
119
import { LocalitySynonymsModal } from './LocalitySynonymsModal'
1210
import { currentDateAsString } from '@/shared/currentDateAsString'
1311
import { matchesCountryOrContinent } from '@/shared/validators/countryContinents'
1412

13+
const LocalitiesMap = lazy(async () => {
14+
const module = await import('../Map/LocalitiesMap')
15+
return { default: module.LocalitiesMap }
16+
})
17+
1518
export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Locality) => void }) => {
1619
const [selectedLocality, setSelectedLocality] = useState<string | undefined>()
1720
const [modalOpen, setModalOpen] = useState<boolean>(false)
@@ -405,14 +408,17 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
405408
}
406409

407410
const svgExport = <T extends MRT_RowData>(table: MRT_TableInstance<T>) => {
408-
const rowData: Locality[] = table.getPrePaginationRowModel().rows.map(row => row.original as unknown as Locality)
409-
const dataString = generateSvg(rowData)
410-
const blob = new Blob([dataString], { type: 'image/svg+xml' })
411-
const url = URL.createObjectURL(blob)
412-
const a = document.createElement('a')
413-
a.href = url
414-
a.download = `localities-map-${currentDateAsString()}.svg`
415-
a.click()
411+
void (async () => {
412+
const rowData: Locality[] = table.getPrePaginationRowModel().rows.map(row => row.original as unknown as Locality)
413+
const { generateSvg } = await import('../Map/generateSvg')
414+
const dataString = generateSvg(rowData)
415+
const blob = new Blob([dataString], { type: 'image/svg+xml' })
416+
const url = URL.createObjectURL(blob)
417+
const a = document.createElement('a')
418+
a.href = url
419+
a.download = `localities-map-${currentDateAsString()}.svg`
420+
a.click()
421+
})()
416422
}
417423

418424
const checkRowRestriction = (row: Locality) => {
@@ -421,7 +427,9 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
421427

422428
return (
423429
<>
424-
<LocalitiesMap localities={filteredLocalities} isFetching={localitiesQueryIsFetching} />
430+
<Suspense fallback={<div />}>
431+
<LocalitiesMap localities={filteredLocalities} isFetching={localitiesQueryIsFetching} />
432+
</Suspense>
425433
<TableView<Locality>
426434
title="Localities"
427435
selectorFn={selectorFn}

frontend/src/components/Locality/Tabs/LocalityTab.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { lazy, Suspense, useState } from 'react'
12
import { Editable, LocalityDetailsType, LocalitySynonym } from '@/shared/types'
23
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
34
import { Grouped, ArrayFrame, HalfFrames } from '@/components/DetailView/common/tabLayoutHelpers'
@@ -7,13 +8,20 @@ import { useForm } from 'react-hook-form'
78
import { EditableTable } from '@/components/DetailView/common/EditableTable'
89
import { EditingModal } from '@/components/DetailView/common/EditingModal'
910
import { emptyOption } from '@/components/DetailView/common/misc'
10-
import { CoordinateSelectionMap } from '@/components/Map/CoordinateSelectionMap'
11-
import { useState } from 'react'
1211
import { convertDmsToDec, convertDecToDms } from '@/util/coordinateConversion'
1312
import { validCountries } from '@/shared/validators/countryList'
14-
import { SingleLocalityMap } from '@/components/Map/SingleLocalityMap'
1513
import { useNotify } from '@/hooks/notification'
1614

15+
const CoordinateSelectionMap = lazy(async () => {
16+
const module = await import('@/components/Map/CoordinateSelectionMap')
17+
return { default: module.CoordinateSelectionMap }
18+
})
19+
20+
const SingleLocalityMap = lazy(async () => {
21+
const module = await import('@/components/Map/SingleLocalityMap')
22+
return { default: module.SingleLocalityMap }
23+
})
24+
1725
type SynonymFormValues = {
1826
synonym: string
1927
}
@@ -228,7 +236,9 @@ export const LocalityTab = () => {
228236
const coordinateButton = (
229237
<EditingModal buttonText="Get Coordinates" onSave={onCoordinateSelectorSave}>
230238
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '1em' }}>
231-
<CoordinateSelectionMap markerCoordinates={markerCoordinates} setMarkerCoordinates={setMarkerCoordinates} />
239+
<Suspense fallback={<div>Loading map...</div>}>
240+
<CoordinateSelectionMap markerCoordinates={markerCoordinates} setMarkerCoordinates={setMarkerCoordinates} />
241+
</Suspense>
232242
</Box>
233243
</EditingModal>
234244
)
@@ -250,7 +260,11 @@ export const LocalityTab = () => {
250260
>
251261
<ArrayFrame array={latlong} title="Latitude & Longitude" />
252262
<Box sx={{ width: '50%' }}>
253-
{hasCoordinates && <SingleLocalityMap decLat={editData.dec_lat} decLong={editData.dec_long} />}
263+
{hasCoordinates && (
264+
<Suspense fallback={<div>Loading map...</div>}>
265+
<SingleLocalityMap decLat={editData.dec_lat} decLong={editData.dec_long} />
266+
</Suspense>
267+
)}
254268
</Box>
255269
</Box>
256270
{!mode.read && coordinateButton}

frontend/src/router/index.tsx

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,79 @@
11
import { Navigate, createBrowserRouter } from 'react-router-dom'
22
import App from '../App'
3-
import { Login } from '../components/Login'
4-
import { EmailPage } from '../components/EmailPage'
5-
import {
6-
crossSearchPage,
7-
frontPage,
8-
localityPage,
9-
museumPage,
10-
personPage,
11-
projectPage,
12-
referencePage,
13-
regionPage,
14-
speciesPage,
15-
timeBoundPage,
16-
timeUnitPage,
17-
} from '../components/pages'
18-
import { ProjectNewPage } from '../pages/ProjectNewPage'
19-
import { ProjectEditPage } from '../pages/projects/ProjectEditPage'
3+
4+
const loadPagesElement = async (
5+
key:
6+
| 'crossSearchPage'
7+
| 'localityPage'
8+
| 'museumPage'
9+
| 'personPage'
10+
| 'projectPage'
11+
| 'referencePage'
12+
| 'regionPage'
13+
| 'speciesPage'
14+
| 'timeBoundPage'
15+
| 'timeUnitPage'
16+
) => {
17+
const pagesModule = await import('../components/pages')
18+
19+
return {
20+
Component: () => pagesModule[key],
21+
}
22+
}
2023

2124
const router = createBrowserRouter([
2225
{
2326
path: '/',
2427
element: <App />,
2528
children: [
26-
{ index: true, element: frontPage },
27-
{ path: 'occurrence/:lid/:speciesId', element: crossSearchPage },
29+
{
30+
index: true,
31+
lazy: async () => {
32+
const { FrontPage } = await import('../components/FrontPage')
33+
return { Component: FrontPage }
34+
},
35+
},
36+
{ path: 'occurrence/:lid/:speciesId', lazy: () => loadPagesElement('crossSearchPage') },
2837
{ path: 'occurrence/:id', element: <Navigate to="/occurrence" replace /> },
29-
{ path: 'occurrence', element: crossSearchPage },
30-
{ path: 'crosssearch/:id?', element: crossSearchPage },
31-
{ path: 'locality/:id?', element: localityPage },
32-
{ path: 'species/:id?', element: speciesPage },
33-
{ path: 'museum/:id?', element: museumPage },
34-
{ path: 'reference/:id?', element: referencePage },
35-
{ path: 'time-unit/:id?', element: timeUnitPage },
36-
{ path: 'time-bound/:id?', element: timeBoundPage },
37-
{ path: 'region/:id?', element: regionPage },
38-
{ path: 'person/:id?', element: personPage },
39-
{ path: 'project/new', element: <ProjectNewPage /> },
40-
{ path: 'project/:id/edit', element: <ProjectEditPage /> },
41-
{ path: 'project/:id?', element: projectPage },
42-
{ path: 'email', element: <EmailPage /> },
43-
{ path: 'login', element: <Login /> },
38+
{ path: 'occurrence', lazy: () => loadPagesElement('crossSearchPage') },
39+
{ path: 'crosssearch/:id?', lazy: () => loadPagesElement('crossSearchPage') },
40+
{ path: 'locality/:id?', lazy: () => loadPagesElement('localityPage') },
41+
{ path: 'species/:id?', lazy: () => loadPagesElement('speciesPage') },
42+
{ path: 'museum/:id?', lazy: () => loadPagesElement('museumPage') },
43+
{ path: 'reference/:id?', lazy: () => loadPagesElement('referencePage') },
44+
{ path: 'time-unit/:id?', lazy: () => loadPagesElement('timeUnitPage') },
45+
{ path: 'time-bound/:id?', lazy: () => loadPagesElement('timeBoundPage') },
46+
{ path: 'region/:id?', lazy: () => loadPagesElement('regionPage') },
47+
{ path: 'person/:id?', lazy: () => loadPagesElement('personPage') },
48+
{
49+
path: 'project/new',
50+
lazy: async () => {
51+
const { ProjectNewPage } = await import('../pages/ProjectNewPage')
52+
return { Component: ProjectNewPage }
53+
},
54+
},
55+
{
56+
path: 'project/:id/edit',
57+
lazy: async () => {
58+
const { ProjectEditPage } = await import('../pages/projects/ProjectEditPage')
59+
return { Component: ProjectEditPage }
60+
},
61+
},
62+
{ path: 'project/:id?', lazy: () => loadPagesElement('projectPage') },
63+
{
64+
path: 'email',
65+
lazy: async () => {
66+
const { EmailPage } = await import('../components/EmailPage')
67+
return { Component: EmailPage }
68+
},
69+
},
70+
{
71+
path: 'login',
72+
lazy: async () => {
73+
const { Login } = await import('../components/Login')
74+
return { Component: Login }
75+
},
76+
},
4477
{ path: '*', element: <div>Page not found.</div> },
4578
],
4679
},

0 commit comments

Comments
 (0)