Enhance response detail fetching to include geometries and improve ca… - #1204
Conversation
WalkthroughThis PR adds optional geometry data fetching to response details retrieval, conditionally enabled for geography datasets. Response details and geometries are now fetched concurrently; geometries are normalised from multiple possible payload shapes and cached within ResponseDetails. ChangesGeometry fetching and caching for geography datasets
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/models/requestData.js (1)
189-208: 💤 Low valueConsider the return value when
geometriesis empty.On line 196, when
getGeometryItemsreturns an empty array, the function returnsresponse.data(the raw API response). This raw object is then passed toResponseDetailsand eventually tonormaliseGeometries, which handles it correctly viadata.geometries ?? data.features. However, this path could also return a non-standard shape thatnormaliseGeometriescannot parse, resulting inundefined.The current behaviour is acceptable since
normaliseGeometrieshas fallback handling, but you may want to add a comment clarifying this intentional pass-through for non-standard response shapes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/models/requestData.js` around lines 189 - 208, The fetchGeometries function may intentionally return the raw response.data when getGeometryItems yields an empty array, which can be a non-standard shape later handled by ResponseDetails/normaliseGeometries; add a concise inline comment inside fetchGeometries (near the `return geometries.length > 0 ? geometries : response.data` line) explaining that returning response.data is intentional to allow ResponseDetails/normaliseGeometries to apply their fallback (`data.geometries ?? data.features`) for non-standard API shapes, and note that callers should handle potential undefined results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/models/responseDetails.js`:
- Around line 289-300: The map callback in the geometries computation returns
the whole item when item.geo is a string, which causes non-string entries in the
resulting string[]; update the branch that checks typeof item?.geo === 'string'
to return item.geo (not item) so the geometries array contains the geometry
string values; verify the map transforming function (used to build geometries)
now consistently returns strings for the item.geo, item.geometry, item.value and
transformed_row cases.
In `@test/unit/responseDetails.test.js`:
- Around line 264-276: The test is asserting the old buggy behavior of
normaliseGeometries; after fixing normaliseGeometries to return item.geo for
objects, update this unit test that constructs a ResponseDetails and calls
getGeometries so its expected result becomes ['POINT (1 2)', 'POINT (3 4)']
(i.e., expect the second element to be the string from the geo field),
referencing the ResponseDetails constructor and
getGeometries/normaliseGeometries behavior to locate the assertion to change.
---
Nitpick comments:
In `@src/models/requestData.js`:
- Around line 189-208: The fetchGeometries function may intentionally return the
raw response.data when getGeometryItems yields an empty array, which can be a
non-standard shape later handled by ResponseDetails/normaliseGeometries; add a
concise inline comment inside fetchGeometries (near the `return
geometries.length > 0 ? geometries : response.data` line) explaining that
returning response.data is intentional to allow
ResponseDetails/normaliseGeometries to apply their fallback (`data.geometries ??
data.features`) for non-standard API shapes, and note that callers should handle
potential undefined results.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: ac45d475-36c3-42a4-a4be-6befa7113dce
📒 Files selected for processing (5)
src/controllers/resultsController.jssrc/models/requestData.jssrc/models/responseDetails.jstest/unit/requestData.test.jstest/unit/responseDetails.test.js
…prove caching; update tests accordingly
…mprove pagination logic
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
test/unit/responseDetails.test.js (1)
287-309: ⚡ Quick winAdd a regression test for paginated geometry request failures.
Current tests only cover the happy path for page 2+ fetches. Please add a case where the second
axios.getrejects, and assertgetGeometries()does not throw (and returns the agreed fallback), so this resilience is protected.✅ Example test shape
+ it('does not throw when a paginated geometry request fails', async () => { + axios.get + .mockResolvedValueOnce({ + headers: { + 'x-pagination-total-results': 3, + 'x-pagination-limit': 2 + }, + data: ['POINT (1 2)', 'POINT (3 4)'] + }) + .mockRejectedValueOnce(new Error('timeout')) + + const responseDetails = new ResponseDetails(1, mockResponse, undefined, undefined) + await expect(responseDetails.getGeometries()).resolves.toEqual(['POINT (1 2)', 'POINT (3 4)']) + })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/unit/responseDetails.test.js` around lines 287 - 309, Add a new unit test in responseDetails.test.js that simulates a failure on the second page fetch by using axios.get.mockResolvedValueOnce(...) for the first page and axios.get.mockRejectedValueOnce(new Error('network')) for the second, then call new ResponseDetails(1, mockResponse, undefined, undefined).getGeometries() and assert it does not throw and returns the accumulated geometries from the successful first page (e.g., ['POINT (1 2)', 'POINT (3 4)']), and also assert axios.get was called with the expected URL for page 2; reference ResponseDetails and getGeometries when locating where to add the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/models/responseDetails.js`:
- Around line 208-213: getGeometries() performs additional axios.get calls in
the pagination loop without error handling, so a failure on page 2+ will reject
the whole call and abort setupTableParams; wrap the per-page fetch (the
axios.get inside the for loop) in a try/catch, handle errors by logging the
error and either skipping that page (continue) or breaking gracefully while
still returning the geometries collected so far, and ensure the function
resolves with partial results instead of rethrowing; update any callers (e.g.,
setupTableParams) expectations if needed to accept partial geometry arrays.
---
Nitpick comments:
In `@test/unit/responseDetails.test.js`:
- Around line 287-309: Add a new unit test in responseDetails.test.js that
simulates a failure on the second page fetch by using
axios.get.mockResolvedValueOnce(...) for the first page and
axios.get.mockRejectedValueOnce(new Error('network')) for the second, then call
new ResponseDetails(1, mockResponse, undefined, undefined).getGeometries() and
assert it does not throw and returns the accumulated geometries from the
successful first page (e.g., ['POINT (1 2)', 'POINT (3 4)']), and also assert
axios.get was called with the expected URL for page 2; reference ResponseDetails
and getGeometries when locating where to add the test.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: ba2d164f-b1af-4cf0-9e78-e9e397db64b5
📒 Files selected for processing (6)
src/controllers/resultsController.jssrc/models/requestData.jssrc/models/responseDetails.jstest/unit/requestData.test.jstest/unit/responseDetails.test.jstest/unit/resultsController.test.js
💤 Files with no reviewable changes (1)
- test/unit/requestData.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
- src/models/requestData.js
| for (let offset = geometries.length; offset < totalResults; offset += limit) { | ||
| const pageUrl = new URL(url) | ||
| pageUrl.searchParams.set('offset', offset) | ||
| pageUrl.searchParams.set('limit', limit) | ||
| const page = await axios.get(pageUrl, { timeout: 30000 }) | ||
| if (!Array.isArray(page.data)) { |
There was a problem hiding this comment.
Paginated geometry fetch can fail the entire results page on later-page request errors.
Line 212 performs follow-up axios.get calls outside a try/catch. If page 2+ fetch fails, getGeometries() rejects and setupTableParams aborts, so users can lose the whole results page even when table data already loaded.
💡 Suggested fix
for (let offset = geometries.length; offset < totalResults; offset += limit) {
const pageUrl = new URL(url)
pageUrl.searchParams.set('offset', offset)
pageUrl.searchParams.set('limit', limit)
- const page = await axios.get(pageUrl, { timeout: 30000 })
+ let page
+ try {
+ page = await axios.get(pageUrl, { timeout: 30000 })
+ } catch (error) {
+ logger.warn('failed to fetch paginated response geometries', {
+ type: types.DataFetch,
+ requestId: this.id,
+ errorMessage: error.message
+ })
+ break
+ }
if (!Array.isArray(page.data)) {
break
}
geometries.push(...page.data)
}
- return geometries
+ return geometries.length > 0 ? geometries : undefined
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (let offset = geometries.length; offset < totalResults; offset += limit) { | |
| const pageUrl = new URL(url) | |
| pageUrl.searchParams.set('offset', offset) | |
| pageUrl.searchParams.set('limit', limit) | |
| const page = await axios.get(pageUrl, { timeout: 30000 }) | |
| if (!Array.isArray(page.data)) { | |
| for (let offset = geometries.length; offset < totalResults; offset += limit) { | |
| const pageUrl = new URL(url) | |
| pageUrl.searchParams.set('offset', offset) | |
| pageUrl.searchParams.set('limit', limit) | |
| let page | |
| try { | |
| page = await axios.get(pageUrl, { timeout: 30000 }) | |
| } catch (error) { | |
| logger.warn('failed to fetch paginated response geometries', { | |
| type: types.DataFetch, | |
| requestId: this.id, | |
| errorMessage: error.message | |
| }) | |
| break | |
| } | |
| if (!Array.isArray(page.data)) { | |
| break | |
| } | |
| geometries.push(...page.data) | |
| } | |
| return geometries.length > 0 ? geometries : undefined |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/models/responseDetails.js` around lines 208 - 213, getGeometries()
performs additional axios.get calls in the pagination loop without error
handling, so a failure on page 2+ will reject the whole call and abort
setupTableParams; wrap the per-page fetch (the axios.get inside the for loop) in
a try/catch, handle errors by logging the error and either skipping that page
(continue) or breaking gracefully while still returning the geometries collected
so far, and ensure the function resolves with partial results instead of
rethrowing; update any callers (e.g., setupTableParams) expectations if needed
to accept partial geometry arrays.
Description
Stops over-fetching
/response-detailsby only loading the current page of row details. For geography datasets, geometries are fetched separately from/requests/:id/geometriesand cached onResponseDetailsso the map can still render all available geometries without scanning every response-detail page.What type of PR is this? (check all applicable)
Related Tickets & Documents
QA Instructions, Screenshots, Recordings
Test the check results page with a large geography dataset, such as the Southampton TPZ endpoint from the ticket.
Confirm that:
/check/results/:id/1loads without timing outtask-logBefore
N/A
After
N/A
Added/updated tests?
We encourage you to keep the code coverage percentage at 80% and above.
Updated unit coverage for:
/response-detailsfetchingResponseDetailsQA sign off
[optional] Are there any post-deployment tasks we need to perform?
None.
[optional] Are there any dependencies on other PRs or Work?
Requires the async request API to expose
/requests/{request_id}/geometries.Summary by CodeRabbit
New Features
Refactor
Tests