Skip to content

Enhance response detail fetching to include geometries and improve ca… - #1204

Merged
gibahjoe merged 4 commits into
mainfrom
2658-stop-over-fetching-response-details
Jun 10, 2026
Merged

Enhance response detail fetching to include geometries and improve ca…#1204
gibahjoe merged 4 commits into
mainfrom
2658-stop-over-fetching-response-details

Conversation

@gibahjoe

@gibahjoe gibahjoe commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Description

Stops over-fetching /response-details by only loading the current page of row details. For geography datasets, geometries are fetched separately from /requests/:id/geometries and cached on ResponseDetails so the map can still render all available geometries without scanning every response-detail page.

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Related Tickets & Documents

  • Closes #2658

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/1 loads without timing out
  • the table shows the current page of response details
  • pagination still works for table rows
  • the map still renders geometries
  • issue/task summaries still come from task-log

Before

N/A

After

N/A

Added/updated tests?

We encourage you to keep the code coverage percentage at 80% and above.

  • Yes
  • No, and this is why: Please replace this line with details on why tests have not been included
  • I need help with writing tests

Updated unit coverage for:

  • current-page-only /response-details fetching
  • geometry-only fetching and pagination
  • cached geometry handling in ResponseDetails

QA sign off

  • Code has been checked and approved
  • Design has been checked and approved
  • Product and business logic has been checked and proved

[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

    • Geography-type datasets can now fetch geometry data asynchronously and cache it for faster subsequent access.
    • Map display is correctly hidden for non-geography datasets.
  • Refactor

    • Pagination now respects the requested page/offset parameters rather than normalising to dataset-wide pagination.
  • Tests

    • Added and updated tests covering geometry fetching, caching and paginated retrieval.

@gibahjoe gibahjoe linked an issue Jun 8, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This 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.

Changes

Geometry fetching and caching for geography datasets

Layer / File(s) Summary
Request options and concurrent fetch
src/models/requestData.js
Add includeGeometries to ResponseDetailsOptions and refactor fetchResponseDetails to request response details and (optionally) geometries concurrently; pass geometriesResponse into ResponseDetails and adjust pagination semantics.
Geometry helpers (iterative fetch & normalisation)
src/models/requestData.js
Introduce fetchGeometries to iteratively fetch paginated geometry pages using pagination headers and getGeometryItems to normalise geometry arrays from different payload shapes; remove the generic fetchPaginated export.
ResponseDetails async getGeometries & caching
src/models/responseDetails.js
Add axios/config imports, private cache fields, and an async getGeometries() that fetches paginated geometry pages, caches results, and returns a normalised array or undefined; remove the old row-derived geometry getter.
Controller async middleware and typology gating
src/controllers/resultsController.js
Refactor setupTableParams to async with try/catch; await responseDetails.getGeometries() when req.locals.datasetTypology === 'geography', otherwise set req.locals.geometries = null.
Tests: requestData, responseDetails, controller
test/unit/requestData.test.js, test/unit/responseDetails.test.js, test/unit/resultsController.test.js
Update tests to remove fetchPaginated import/test, assert current-page pagination semantics, add geometry-only endpoint tests (including paginated follow-ups, URL/timeout checks, and caching), and make controller tests await the async middleware.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

bug

Suggested reviewers

  • rosado
  • eveleighoj

"I'm a rabbit with a tiny fetcher's hat,
Geometries gathered, paginated and sat,
Cached in the burrow for quicker display,
Typology checks keep the map at bay,
Hooray for rows that now neatly map!"

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title is truncated and incomplete, ending with 'improve ca…' which appears to be cut off mid-word, making it impossible to assess whether it accurately summarizes the main change. Provide the complete, untruncated pull request title that clearly summarises the primary objectives without ellipsis or truncation.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2658-stop-over-fetching-response-details

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 66.2% 7122 / 10758
🔵 Statements 66.2% 7122 / 10758
🔵 Functions 63.37% 289 / 456
🔵 Branches 77.49% 995 / 1284
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/controllers/resultsController.js 61.29% 70.83% 56.66% 61.29% 21-40, 57-65, 78-82, 86-100, 108-109, 111-112, 116-117, 154-156, 187-195, 207-208, 237-238, 248-249, 258-266, 274-277, 280-299, 376-378, 394-400, 408-425, 432-452, 464-474, 477-479, 488-501, 543-562
src/models/requestData.js 91.66% 83.33% 91.66% 91.66% 43-47, 121-122, 176-181
src/models/responseDetails.js 93.42% 89.47% 100% 93.42% 81-82, 149-150, 188-194, 199-200, 214-215
Generated in workflow #1497 for commit beae29b by the Vitest Coverage Report Action

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/models/requestData.js (1)

189-208: 💤 Low value

Consider the return value when geometries is empty.

On line 196, when getGeometryItems returns an empty array, the function returns response.data (the raw API response). This raw object is then passed to ResponseDetails and eventually to normaliseGeometries, which handles it correctly via data.geometries ?? data.features. However, this path could also return a non-standard shape that normaliseGeometries cannot parse, resulting in undefined.

The current behaviour is acceptable since normaliseGeometries has 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

📥 Commits

Reviewing files that changed from the base of the PR and between c1d0b66 and d134d38.

📒 Files selected for processing (5)
  • src/controllers/resultsController.js
  • src/models/requestData.js
  • src/models/responseDetails.js
  • test/unit/requestData.test.js
  • test/unit/responseDetails.test.js

Comment thread src/models/responseDetails.js Outdated
Comment thread test/unit/responseDetails.test.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/unit/responseDetails.test.js (1)

287-309: ⚡ Quick win

Add 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.get rejects, and assert getGeometries() 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

📥 Commits

Reviewing files that changed from the base of the PR and between d134d38 and beae29b.

📒 Files selected for processing (6)
  • src/controllers/resultsController.js
  • src/models/requestData.js
  • src/models/responseDetails.js
  • test/unit/requestData.test.js
  • test/unit/responseDetails.test.js
  • test/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

Comment on lines +208 to +213
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)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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.

@gibahjoe
gibahjoe merged commit 95cc25e into main Jun 10, 2026
7 of 8 checks passed
@gibahjoe
gibahjoe deleted the 2658-stop-over-fetching-response-details branch June 10, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stop over fetching response-details

2 participants