Skip to content

feat: Implement endpoint duplication check in CheckConfirmationContro… - #1215

Merged
gibahjoe merged 9 commits into
mainfrom
1198-dont-allow-duplicate-endpoint-to-be-submitted
Jul 1, 2026
Merged

feat: Implement endpoint duplication check in CheckConfirmationContro…#1215
gibahjoe merged 9 commits into
mainfrom
1198-dont-allow-duplicate-endpoint-to-be-submitted

Conversation

@gibahjoe

@gibahjoe gibahjoe commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a new /check/confirmation variant for endpoint URLs that are already being collected for the checked dataset. The page now shows “Data checked” content and returns users to the dataset overview instead of asking them to submit the endpoint URL again.

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

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

Related Tickets & Documents

QA Instructions, Screenshots, Recordings

Test by checking a URL that is already collected for the same dataset. After continuing from the check results page, /check/confirmation should show the “Data checked” panel and explain that the endpoint URL does not need to be submitted again.

Also test a URL that is not already collected. The existing “Provide your data” confirmation page and submit button should still appear.

Before

The confirmation page always showed “Provide your data” for URL checks, even when the endpoint URL was already being collected.

After

The confirmation page shows “Data checked” and explains that updates will be collected automatically when the endpoint URL is already collected for the dataset.

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

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?

None.

Summary by CodeRabbit

  • New Features
    • Confirmation pages now detect when an endpoint is already being collected, adjusting titles, GOV.UK panel messaging, and “What happens next” text, and adding automatic return links.
    • Status polling now supports optional unique dataset fields, and the status endpoint can include column-mapping information based on those fields.
  • Bug Fixes
    • URL-check flows are more resilient: request lookups failing no longer prevent confirmation context from being set.
  • Tests
    • Added/expanded unit tests covering endpoint already-collected behaviour, organisation scoping and query escaping, and status/query/polling updates.

…ller

- Update CheckConfirmationController to check if an endpoint is already being collected for a dataset.
- Modify confirmation.html to display appropriate messages based on endpoint collection status.
- Add unit tests for CheckConfirmationController and endpointAlreadyCollectedForDataset functionality.
- Introduce endpointAlreadyCollectedForDataset utility to query the database for existing endpoints.
@gibahjoe gibahjoe linked an issue Jun 29, 2026 that may be closed by this pull request
6 tasks
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 70.98% 2540 / 3578
🔵 Statements 70.14% 2679 / 3819
🔵 Functions 64.13% 506 / 789
🔵 Branches 63.92% 1249 / 1954
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/assets/js/statusPage.js 89.06% 85.71% 84.61% 91.93% 41-42, 96, 103, 111-113
src/controllers/checkConfirmationController.js 100% 80% 100% 100%
src/controllers/statusController.js 51.02% 54.16% 57.14% 51.06% 20-56, 69, 95-97
src/routes/api.js 69.23% 64.28% 33.33% 70.83% 37, 44, 61-65, 69-73
src/utils/datasetteQueries/endpointAlreadyCollected.js 100% 100% 100% 100%
Generated in workflow #1548 for commit 548b7de by the Vitest Coverage Report Action

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds duplicate-endpoint handling on the confirmation flow and threads unique dataset fields through the status API, controller, view, and polling tests.

Changes

Duplicate Endpoint Detection Flow

Layer / File(s) Summary
Datasette duplicate-endpoint helper
src/utils/datasetteQueries/endpointAlreadyCollected.js, test/unit/utils/endpointAlreadyCollected.test.js
Adds a Datasette lookup for active endpoint, dataset, and organisation matches, with escaping, input guards, and tests for matches, scoping, escaping, and missing inputs.
Async confirmation locals
src/controllers/checkConfirmationController.js, test/unit/checkConfirmationController.test.js
Makes confirmation locals async, fetches request data for URL checks, stores dataset and organisation in session state, checks whether the endpoint is already collected, and conditionally clears or sets the check request id.
Confirmation already-collecting branch
src/views/check/confirmation.html, test/unit/check/confirmationPage.test.js
Adds an already-collecting page state, derives overview links, renders alternate next-step copy, and covers the new return-link behaviour in tests.

Status Polling and Field Querying

Layer / File(s) Summary
Status route field parsing
src/routes/api.js, test/unit/routes.api.test.js
Exports the status handler and field parser, reads field query values into uniqueDatasetFields, and passes them into the column-mapping decision.
Status polling endpoint
src/controllers/statusController.js, test/unit/statusController.test.js
Builds the polling endpoint with repeated field= query parameters from req.uniqueDatasetFields.
Status page rendering and polling timing
src/assets/js/statusPage.js, src/views/check/statusPage/status.html, test/unit/statusPage.test.js
Changes the default polling interval, serialises the polling endpoint as JavaScript data, and updates polling tests to use the configured interval.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant CheckConfirmationController
  participant asyncRequestApi
  participant endpointAlreadyCollectedForDataset
  participant datasette

  Browser->>CheckConfirmationController: request confirmation page
  CheckConfirmationController->>asyncRequestApi: getRequestData(requestId)
  asyncRequestApi-->>CheckConfirmationController: request params
  CheckConfirmationController->>endpointAlreadyCollectedForDataset: endpointUrl, dataset, organisation
  endpointAlreadyCollectedForDataset->>datasette: runQuery(SQL)
  datasette-->>endpointAlreadyCollectedForDataset: formattedData
  endpointAlreadyCollectedForDataset-->>CheckConfirmationController: alreadyCollectingEndpoint
  CheckConfirmationController->>CheckConfirmationController: set or delete req.session.checkRequestId
  CheckConfirmationController-->>Browser: render confirmation page
Loading
sequenceDiagram
  participant Client
  participant getStatus
  participant shouldShowColumnMapping
  participant StatusController
  participant StatusPage
  Client->>getStatus: GET /status/:result_id?field=...
  getStatus->>shouldShowColumnMapping: request data + uniqueDatasetFields
  shouldShowColumnMapping-->>getStatus: showColumnMapping decision
  getStatus-->>Client: response body with columnMappingUrl
  Client->>StatusController: build pollingEndpoint
  StatusController-->>Client: /api/status/:id?field=...
  Client->>StatusPage: poll at configured interval
  StatusPage-->>Client: update polling state
Loading

Suggested labels: enhancement

Suggested reviewers: rosado, DilwoarH

Poem

🐇 I hopped through checks both old and new,
with fields and endpoints tagged just so.
If one is live, the page will say,
“You’re already here — no need to re-send today!”

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Several status-page/API polling changes and tests are unrelated to duplicate endpoint confirmation handling. Split the status polling and column-mapping changes into a separate PR, or explain how they are required for the duplicate endpoint flow.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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.
Title check ✅ Passed The title is concise and clearly points to the duplicate endpoint check in the confirmation flow.
Linked Issues check ✅ Passed The confirmation flow now detects active matching endpoints, hides submit, and shows reassurance only for the same dataset and organisation.
✨ 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 1198-dont-allow-duplicate-endpoint-to-be-submitted

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.

@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)
test/unit/utils/endpointAlreadyCollected.test.js (1)

16-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a cross-organisation regression case.

The new behaviour is only meant to short-circuit within the same provision. A negative test for “same URL + same dataset + different organisation” would protect that contract and catch the current over-broad lookup.

🤖 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/utils/endpointAlreadyCollected.test.js` around lines 16 - 53, Add a
regression test in endpointAlreadyCollectedForDataset to cover the same
endpointUrl and dataset belonging to a different organisation/provision, and
assert it returns false. The current tests in endpointAlreadyCollected.test.js
only cover matching, non-matching, escaping, and missing input; extend the
Datasette mock setup so the lookup stays scoped to the same provision and does
not short-circuit across organisations. Use the existing
endpointAlreadyCollectedForDataset helper and datasette.runQuery mock in the new
case.
🤖 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/utils/datasetteQueries/endpointAlreadyCollected.js`:
- Around line 7-20: The duplicate-check query in
endpointAlreadyCollectedForDataset is too broad because it only matches on
endpointUrl and dataset. Update this helper to accept the organisation/provision
key as an additional input, pass it through the call sites, and add it to the
WHERE clause in the SQL so the lookup is scoped to the same
organisation/provision as the submission.

In `@src/views/check/confirmation.html`:
- Around line 17-23: The fallback handling for datasetOverviewHref in the
confirmation template is mismatched with the link copy: when there is no
deepLink or orgId, it currently points to “/” while still using the overview
wording. Update the conditional around datasetOverviewHref and the related link
label so the Home copy is used in that branch, or only keep the overview label
when you can construct a real overview URL from options.deepLink, options.orgId,
and options.dataset.

---

Nitpick comments:
In `@test/unit/utils/endpointAlreadyCollected.test.js`:
- Around line 16-53: Add a regression test in endpointAlreadyCollectedForDataset
to cover the same endpointUrl and dataset belonging to a different
organisation/provision, and assert it returns false. The current tests in
endpointAlreadyCollected.test.js only cover matching, non-matching, escaping,
and missing input; extend the Datasette mock setup so the lookup stays scoped to
the same provision and does not short-circuit across organisations. Use the
existing endpointAlreadyCollectedForDataset helper and datasette.runQuery mock
in the new case.
🪄 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: b76550b6-315b-42c6-9a1b-2c19cd2b428c

📥 Commits

Reviewing files that changed from the base of the PR and between 9ca58c6 and 330eb07.

📒 Files selected for processing (6)
  • src/controllers/checkConfirmationController.js
  • src/utils/datasetteQueries/endpointAlreadyCollected.js
  • src/views/check/confirmation.html
  • test/unit/check/confirmationPage.test.js
  • test/unit/checkConfirmationController.test.js
  • test/unit/utils/endpointAlreadyCollected.test.js

Comment thread src/utils/datasetteQueries/endpointAlreadyCollected.js Outdated
Comment thread src/views/check/confirmation.html

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

🧹 Nitpick comments (2)
test/unit/utils/endpointAlreadyCollected.test.js (1)

76-83: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test title claims "endpoint URL or dataset is missing" but only exercises the missing-endpointUrl case.

The dataset short-circuit branch isn't separately verified here.

♻️ Suggested addition
   it('does not query Datasette when endpoint URL or dataset is missing', async () => {
     await expect(endpointAlreadyCollectedForDataset({
       endpointUrl: '',
       dataset: 'brownfield-land'
     })).resolves.toBe(false)

     expect(datasette.runQuery).not.toHaveBeenCalled()
   })
+
+  it('does not query Datasette when dataset is missing', async () => {
+    await expect(endpointAlreadyCollectedForDataset({
+      endpointUrl: 'https://example.com/data.csv',
+      dataset: ''
+    })).resolves.toBe(false)
+
+    expect(datasette.runQuery).not.toHaveBeenCalled()
+  })
🤖 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/utils/endpointAlreadyCollected.test.js` around lines 76 - 83, The
test name in endpointAlreadyCollectedForDataset is broader than the behavior it
covers, since it only checks the missing endpointUrl path. Update the test suite
in endpointAlreadyCollected.test.js to also exercise the missing dataset
short-circuit branch, ideally as a separate assertion or dedicated test
alongside the existing endpointUrl case, and keep the expectation that
datasette.runQuery is not called.
test/unit/check/confirmationPage.test.js (1)

93-109: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Negative assertion may be trivially true regardless of correctness.

The check expect(doc.body.textContent).not.toContain('Return to brownfield-land overview') uses the raw dataset slug, but the template applies options.dataset | datasetSlugToReadableName before rendering the link text. If that filter reformats the slug (e.g. to "Brownfield land"), the literal string being asserted absent would never appear anyway — even if the "already collecting" overview link were incorrectly rendered instead of the home-link fallback. This makes the negative assertion a weak (potentially tautological) guard.

Consider asserting on the anchor's href (e.g. no a[href^="/organisations/"]) or on the filtered text, to make sure the test actually catches a regression where the overview link renders unexpectedly.

Suggested strengthening
       expect(homeLink).not.toBeNull()
       expect(homeLink.textContent.trim()).toBe('Return to Home')
-      expect(doc.body.textContent).not.toContain('Return to brownfield-land overview')
+      expect(doc.querySelector('a[href^="/organisations/"]')).toBeNull()
🤖 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/check/confirmationPage.test.js` around lines 93 - 109, The test in
confirmationPage.test.js uses a weak negative assertion against the raw dataset
slug, which can pass even if the overview link renders incorrectly. Update the
assertion in the confirmation page test to verify the rendered anchor target or
the transformed link text from check/confirmation.html, using the existing JSDOM
query pattern and the alreadyCollectingEndpoint/options.dataset setup, so the
test fails if an organisations overview link appears instead of the home
fallback.
🤖 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.

Nitpick comments:
In `@test/unit/check/confirmationPage.test.js`:
- Around line 93-109: The test in confirmationPage.test.js uses a weak negative
assertion against the raw dataset slug, which can pass even if the overview link
renders incorrectly. Update the assertion in the confirmation page test to
verify the rendered anchor target or the transformed link text from
check/confirmation.html, using the existing JSDOM query pattern and the
alreadyCollectingEndpoint/options.dataset setup, so the test fails if an
organisations overview link appears instead of the home fallback.

In `@test/unit/utils/endpointAlreadyCollected.test.js`:
- Around line 76-83: The test name in endpointAlreadyCollectedForDataset is
broader than the behavior it covers, since it only checks the missing
endpointUrl path. Update the test suite in endpointAlreadyCollected.test.js to
also exercise the missing dataset short-circuit branch, ideally as a separate
assertion or dedicated test alongside the existing endpointUrl case, and keep
the expectation that datasette.runQuery is not called.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c6a4f2c6-7051-4eb0-8b1f-74b9667a5623

📥 Commits

Reviewing files that changed from the base of the PR and between d4873b0 and 9fac5cf.

📒 Files selected for processing (3)
  • src/views/check/confirmation.html
  • test/unit/check/confirmationPage.test.js
  • test/unit/utils/endpointAlreadyCollected.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/views/check/confirmation.html

…on and update confirmation view link expectations

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

🧹 Nitpick comments (1)
test/unit/check/confirmationPage.test.js (1)

35-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated heading-assertion structure across the two describe blocks.

Both tests assert the same h1/h2/h3 pattern with only the expected text differing. A small shared helper (e.g. expectHeadingStructure(doc, { h1, h3s })) would reduce duplication, though the current form is still clear and easy to follow.

Also applies to: 62-70

🤖 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/check/confirmationPage.test.js` around lines 35 - 43, The
confirmation page heading assertions are duplicated across the two describe
blocks, so extract the shared DOM checks into a small helper used by both tests.
Update the test file around the existing heading assertions in the
confirmationPage suite, and centralize the repeated `main h1`/`main h2`/`main
h3` expectations in a helper like `expectHeadingStructure` that accepts the
expected text values.
🤖 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.

Nitpick comments:
In `@test/unit/check/confirmationPage.test.js`:
- Around line 35-43: The confirmation page heading assertions are duplicated
across the two describe blocks, so extract the shared DOM checks into a small
helper used by both tests. Update the test file around the existing heading
assertions in the confirmationPage suite, and centralize the repeated `main
h1`/`main h2`/`main h3` expectations in a helper like `expectHeadingStructure`
that accepts the expected text values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ad710878-8c99-4d96-bdfb-7e7f2fbee194

📥 Commits

Reviewing files that changed from the base of the PR and between d23362b and 548b7de.

📒 Files selected for processing (4)
  • src/utils/datasetteQueries/endpointAlreadyCollected.js
  • src/views/check/confirmation.html
  • test/unit/check/confirmationPage.test.js
  • test/unit/utils/endpointAlreadyCollected.test.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/unit/utils/endpointAlreadyCollected.test.js
  • src/utils/datasetteQueries/endpointAlreadyCollected.js
  • src/views/check/confirmation.html

@pooleycodes
pooleycodes self-requested a review July 1, 2026 12:59

@pooleycodes pooleycodes 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.

LGTM

@gibahjoe
gibahjoe merged commit 55e10d6 into main Jul 1, 2026
5 checks passed
@gibahjoe
gibahjoe deleted the 1198-dont-allow-duplicate-endpoint-to-be-submitted branch July 1, 2026 13:49
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.

Don't allow duplicate endpoint to be submitted

2 participants