Skip to content

Show recently updated capture sets first in pickers, and stop them waiting on counts they never show - #1381

Merged
mihow merged 8 commits into
mainfrom
fix/capture-set-picker-choices
Aug 1, 2026
Merged

Show recently updated capture sets first in pickers, and stop them waiting on counts they never show#1381
mihow merged 8 commits into
mainfrom
fix/capture-set-picker-choices

Conversation

@mihow

@mihow mihow commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Picking a capture set when starting a job is meant to be the easy part of the form, and
in a project that has accumulated a lot of capture sets it currently is not. The dropdown
loads one page of results in whatever order the database happens to return them, so a set
a user created minutes ago may not be in the list at all, and there is no way to reach it
from the form. The same dropdown also waits on the number of captures, occurrences and
taxa in every capture set in the project — figures it never displays — which is why it
sits disabled for a long time before it becomes usable.

This changes both. Capture sets now come back most recently updated first, so the sets
someone is actively working with are at the top of every picker and filter. And the
pickers read from a new endpoint that returns just enough to name a capture set, so they
no longer pay for counts they do not show. That endpoint sends a project's capture sets
in a single response rather than in pages, because a dropdown has no way to ask for a
second one. It follows the pattern introduced for the occurrence algorithm filter in
#1368: a small, purpose-built sub-action that serves a filter's choices, separate from
the full list endpoint the table page uses.

Reported against a project whose capture set list had grown past one page.

List of Changes

# Change How
1 Capture sets are listed most recently updated first, in the job form, the export form, the capture set filter, and the API by default ordering = ["-updated_at"] on SourceImageCollectionViewSet. The capture sets table page asks for its own sort order and is unaffected
2 Capture set dropdowns no longer wait on counts they never display New GET /api/v2/captures/collections/choices/ returning id, name and sampling method per set, serialized with the existing SourceImageCollectionNestedSerializer. CaptureSetPicker and CaptureSetFilter read from it
3 A dropdown receives a project's capture sets in one response and never has to page CaptureSetChoicesPagination sets both the default and the maximum size to 100, so the response size is the same whether or not a caller sends a limit. No pagination arguments are passed from the frontend at all
4 The job and export form still reports how many captures the selected set holds useCaptureSetDetails fetches the selected set on its own, the same shape the taxon filter uses for its selected taxon
5 A capture set that is already selected stays visible in the picker, even in a project large enough for it to fall outside the choices The picker adds the selected set back into its options when the choices do not contain it
6 A selection pointing at a deleted capture set is cleared instead of being submitted while the field looks empty Once both the choices request and the single-set request have settled without finding it, the picker reports the value upwards as cleared
7 Pages of capture sets are stable across requests The viewset had no default ordering at all, so page boundaries were undefined
8 Contributor guidance for the frontend records where a shared constant belongs and how long a comment should be ui/AGENTS.md. Docs only, no behaviour change — happy to split it out if you would rather review it separately

Capture choices load almost instantaneously now:

image image

Detailed Description

The capture set list endpoint annotates three counts on every row —
source_images_count, source_images_with_detections_count and
source_images_processed_count — each of which joins through captures to detections and
de-duplicates. Grouping happens before LIMIT, so asking for 20 rows still costs the
whole project. Two measurements from a local copy of production data, on a project with
46 capture sets:

Query Time
list(qs[:20]) with no count annotations 0.022s
list(qs[:20]) with the three count annotations 24.8s

Over HTTP on the same machine and project, the choices endpoint answered in 0.9s against
2.7s for the list endpoint once both were warm, and 6.1s for the list endpoint on the
first request after a restart. Those figures include local development middleware and a
warm query cache, so treat them as indicative of the gap; the ORM timings above are the
direct measurement.

The counts themselves are untouched — the capture sets table still reports all of them,
and ?with_counts=true still adds occurrence and taxa counts. The annotations simply
moved from the viewset's class-level queryset into get_queryset() so the choices action
can opt out of them. Sorting by a count is still available on the list endpoint and is
excluded from the choices action, where the annotations do not exist.

The page size lives on the server rather than with each caller. A dropdown cannot page,
so any size a caller picks is either too small to hold a project's capture sets or an
arbitrary guess, and different callers had been picking differently. max_limit is set
alongside default_limit so the size is a contract rather than a default: no caller can
raise it, and the response stays bounded for a project of any size. The total count in
the response still reports every capture set, so a caller can tell when it has received
a subset.

Testing

  • ami.main.tests and ami.exports pass locally, alongside CI.
  • TestCaptureSetChoices (9 tests) pins the contract the pickers depend on: most
    recently updated first, names without counts, scoped to the requested project, a
    project is required, capture sets in a draft project stay hidden from non-members, one
    capped response whether or not a limit is sent, and the list endpoint keeping both its
    counts and its ability to sort by them.
  • Frontend: tsc --noEmit, eslint and prettier --check all clean.
  • No model change, so no migration. makemigrations --check passes.

Verified in a browser

Clicked through against a local copy of production data, on a project with 46 capture
sets:

  • Job form — the picker requests /captures/collections/choices/?project_id=… with
    no pagination arguments and lists all 46 sets, most recently updated first. Selecting
    one fetches /captures/collections/{id}/?project_id=…&with_counts=false and shows
    "This will select 1,000 captures."
  • Occurrences and jobs filter sidebar — reads the same endpoint and shows the same
    order.
  • Capture sets table page — unchanged; it asks for its own sort order and still shows
    every count column.

Not exercised in a browser: the 100 capture set cap, since no project on hand has that
many. It is covered by a test that creates 120 and asserts the response size with and
without a limit argument.

Follow-up

#1380 proposes replacing the dropdown with a searchable field like the taxon filter, which
is the real fix for a project with more capture sets than one response can hold. This
change makes the common case work; it does not make an arbitrarily long list navigable.

mihow and others added 2 commits July 27, 2026 07:23
The capture set list endpoint reports the number of captures, occurrences and
taxa in each set. Filters and pickers only need to name a set, so serving their
options from that endpoint made every dropdown wait on counts nobody reads.

/api/v2/captures/collections/choices/ returns the id, name and sampling method
of each capture set in a project, following the same pattern as the occurrence
algorithm filter's choices at /occurrences/algorithms/. The list endpoint keeps
its counts.

The viewset also gains a default ordering of most recently updated first. It had
none, so row order came back in whatever order Postgres produced, which left
pagination unstable and could put a capture set a user had just created outside
the first page of a picker.

Co-Authored-By: Claude <noreply@anthropic.com>
The job form, the export form and the capture set filter now read their options
from /captures/collections/choices/, so they no longer wait on counts they do
not display, and the most recently updated capture sets come first.

The job and export picker still shows how many captures the selected set holds.
That count now comes from a single-record fetch of the selected set, the same
shape the taxon filter uses for its selected taxon. A set that was picked before
it fell off the end of the list is added back to the options, so an existing
selection never disappears from the form.

Co-Authored-By: Claude <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 27, 2026

Copy link
Copy Markdown

Deploy Preview for antenna-preview canceled.

Name Link
🔨 Latest commit 3e1d55f
🔍 Latest deploy log https://app.netlify.com/projects/antenna-preview/deploys/6a6e14a8d94452000888c9ee

@netlify

netlify Bot commented Jul 27, 2026

Copy link
Copy Markdown

Deploy Preview for antenna-ssec canceled.

Name Link
🔨 Latest commit 3e1d55f
🔍 Latest deploy log https://app.netlify.com/projects/antenna-ssec/deploys/6a6e14a8d7a60c0008210f86

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The backend adds a project-scoped, capped choices endpoint for capture sets. The frontend uses this endpoint for filtering and loads selected-set details separately. Existing list counts and ordering remain supported.

Changes

Capture set choices

Layer / File(s) Summary
Backend choices endpoint and regression coverage
ami/main/api/views.py, ami/main/tests.py
The collection viewset adds capped project-scoped choices, recent-update ordering, conditional count annotations, and lightweight serialization. Tests cover scoping, visibility, fields, ordering, and limits.
Frontend capture-set data access
ui/src/data-services/constants.ts, ui/src/data-services/hooks/capture-sets/useCaptureSetDetails.ts, ui/src/components/filtering/filters/capture-set-filter.tsx, ui/AGENTS.md
The frontend defines and uses the choices route. useCaptureSetDetails fetches selected sets without occurrence or taxa counts. Frontend guidance documents constant and comment conventions.
Picker choices and selection handling
ui/src/nova-ui-kit/components/select/capture-set-picker.tsx
CaptureSetPicker loads up to 200 choices, preserves selected sets outside the choices, clears confirmed missing selections, and combines choice and detail state for rendering.

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

Sequence Diagram(s)

sequenceDiagram
  participant CaptureSetPicker
  participant useEntities
  participant ChoicesAPI
  participant useCaptureSetDetails
  CaptureSetPicker->>useEntities: load up to 200 capture-set choices
  useEntities->>ChoicesAPI: request project-scoped choices
  ChoicesAPI-->>useEntities: return lightweight choices
  CaptureSetPicker->>useCaptureSetDetails: load selected capture set by IDs
  useCaptureSetDetails-->>CaptureSetPicker: return selected capture-set details
Loading

Possibly related PRs

Suggested reviewers: annavik

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly summarizes the primary changes to picker ordering and removal of unnecessary count loading.
Description check ✅ Passed The description covers the changes, rationale, testing, screenshots, performance effects, limitations, and deployment impact in detail.
✨ 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 fix/capture-set-picker-choices

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.

@mihow
mihow marked this pull request as ready for review August 1, 2026 07:30
Copilot AI review requested due to automatic review settings August 1, 2026 07:30

Copilot AI 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.

🟡 Not ready to approve

The new choices action assumes pagination is always enabled and the capture set filter UI can lose visibility of an active selection when it falls outside the loaded page.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR improves capture set selection UX/performance by making capture set lists default to “most recently updated first” and adding a lightweight “choices” endpoint for dropdowns/filters so they don’t block on expensive count annotations.

Changes:

  • Add default -updated_at ordering for capture sets and introduce GET /api/v2/captures/collections/choices/ that skips count annotations.
  • Update UI pickers/filters to consume the new choices endpoint, and add a dedicated hook to fetch a single selected capture set when counts are needed.
  • Add API tests that pin the choices endpoint contract and ensure the list endpoint still supports counts and count-based ordering.
File summaries
File Description
ui/src/nova-ui-kit/components/select/capture-set-picker.tsx Switch picker to /choices/, add selected-item fallback, and fetch selected-set details separately for the capture count message.
ui/src/data-services/hooks/capture-sets/useCaptureSetDetails.ts New hook to fetch a single capture set (without occurrence/taxa counts) for selected-set display.
ui/src/data-services/constants.ts Add CAPTURE_SET_CHOICES route constant.
ui/src/components/filtering/filters/capture-set-filter.tsx Point capture set filter to /choices/ endpoint.
ami/main/tests.py Add TestCaptureSetChoices to pin ordering/scoping/shape and guard list endpoint behavior.
ami/main/api/views.py Move expensive annotations into get_queryset(), add default ordering, and implement the new choices action.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 2
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread ami/main/api/views.py Outdated
Comment thread ui/src/components/filtering/filters/capture-set-filter.tsx
Both sides appended a test class to the end of ami/main/tests.py, so the
resolution keeps both: the capture set choices tests from this branch and
the bulk identification tests from #1371.

Co-Authored-By: Claude <noreply@anthropic.com>

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ui/src/components/filtering/filters/capture-set-filter.tsx (1)

5-17: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Preserve a previously selected capture set in CaptureSetFilter.

EntityPicker only uses the first choices page, then clears _value unless entities.some(...) matches, so a previously selected set outside that page makes the filter read-only empty and removes the selection from view. Apply the same behavior as CaptureSetPicker: load the selected capture set separately when it is not in the loaded choices and include it in the displayed options.

🤖 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 `@ui/src/components/filtering/filters/capture-set-filter.tsx` around lines 5 -
17, Update CaptureSetFilter to preserve a selected value that is absent from the
initial CAPTURE_SET_CHOICES page, matching CaptureSetPicker’s behavior. Load the
selected capture set separately when needed and include it in the options passed
to EntityPicker so the existing selection remains visible and editable.
🤖 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 `@ui/src/nova-ui-kit/components/select/capture-set-picker.tsx`:
- Around line 38-43: Add an effect in the capture-set picker that, after both
data fetches settle, calls onValueChange(undefined) when _value is non-empty but
absent from choices. Keep the existing fallback that preserves captureSet during
loading, and avoid clearing until loading has completed so transient fetch
states do not erase a valid selection.

---

Outside diff comments:
In `@ui/src/components/filtering/filters/capture-set-filter.tsx`:
- Around line 5-17: Update CaptureSetFilter to preserve a selected value that is
absent from the initial CAPTURE_SET_CHOICES page, matching CaptureSetPicker’s
behavior. Load the selected capture set separately when needed and include it in
the options passed to EntityPicker so the existing selection remains visible and
editable.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cc4b2fa7-361c-4ab6-9a97-7b7ac043bd36

📥 Commits

Reviewing files that changed from the base of the PR and between 731cc2a and 4ff7292.

📒 Files selected for processing (6)
  • ami/main/api/views.py
  • ami/main/tests.py
  • ui/src/components/filtering/filters/capture-set-filter.tsx
  • ui/src/data-services/constants.ts
  • ui/src/data-services/hooks/capture-sets/useCaptureSetDetails.ts
  • ui/src/nova-ui-kit/components/select/capture-set-picker.tsx

Comment thread ui/src/nova-ui-kit/components/select/capture-set-picker.tsx Outdated
mihow and others added 3 commits August 1, 2026 00:49
…t when it is gone

The capture set filter loaded only the first page of options and showed a blank
field whenever the selected set was not on it. It now loads the same number of
options as the job form, and both share one constant for that limit.

The job form picker clears its value once both of its fetches have settled and
the selected set is still missing from the choices, so a set that has been
deleted cannot be submitted while the field looks empty.

Co-Authored-By: Claude <noreply@anthropic.com>
…this repo

The comments explaining the choices limit repeated each other across three files
and ran longer than the surrounding code. Each reason is now stated once, in the
place a future editor will be looking, with the ticket carrying the rest.

Co-Authored-By: Claude <noreply@anthropic.com>
…kip ci]

The rule in .agents/AGENTS.md is written for the Python side, where two or three
lines of rationale sit comfortably. Most components here carry none, so the same
block stands out far more, and reviewers have had to ask for the same trim more
than once.

Records the leaner frontend norm, where a shared constant belongs versus a
module-local one, and a way to compare a file's comment density with its
neighbours before opening a PR.

Co-Authored-By: Claude <noreply@anthropic.com>

@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

🤖 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 `@ui/src/nova-ui-kit/components/select/entity-picker.tsx`:
- Around line 20-22: Update the entity loading and value derivation in the
entity picker around useEntities and the current value calculation so selected
entities not included in the paginated first page are fetched or merged into the
available entities before deriving value. Preserve valid selections, and only
clear a selection after the API confirms the corresponding entity was deleted.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: aba0e699-0a1f-425c-9ee0-b49b145fd4e6

📥 Commits

Reviewing files that changed from the base of the PR and between 4ff7292 and 16608f0.

📒 Files selected for processing (7)
  • ami/main/api/views.py
  • ami/main/tests.py
  • ui/AGENTS.md
  • ui/src/components/filtering/filters/capture-set-filter.tsx
  • ui/src/data-services/constants.ts
  • ui/src/nova-ui-kit/components/select/capture-set-picker.tsx
  • ui/src/nova-ui-kit/components/select/entity-picker.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • ui/src/components/filtering/filters/capture-set-filter.tsx
  • ui/src/data-services/constants.ts
  • ami/main/tests.py
  • ui/src/nova-ui-kit/components/select/capture-set-picker.tsx
  • ami/main/api/views.py

Comment thread ui/src/nova-ui-kit/components/select/entity-picker.tsx Outdated
mihow and others added 2 commits August 1, 2026 08:43
…wn gets

Callers were each asking for a page size, which is a decision none of them are
in a position to make: a dropdown cannot page, so any number they pick is either
too small to hold a project's capture sets or an arbitrary guess. The choices
endpoint now sends up to a hundred in one response, ordered most recently updated
first, and refuses to send more however large a limit is requested.

The frontend no longer passes pagination anywhere. Reaching capture sets beyond
the cap is what the search field in #1380 is for.

Co-Authored-By: Claude <noreply@anthropic.com>
Two tests covered the response size, one below the cap and one above it, and the
below-cap case is already covered by the ordering test. They are now a single
test asserting that the size does not change whether or not a caller sends a
limit, which is the property the endpoint actually promises.

Also drops an assertion that the other project's capture set is absent, since
the test above it already compares the returned ids to an exact set.

Co-Authored-By: Claude <noreply@anthropic.com>
@mihow
mihow merged commit ffefa68 into main Aug 1, 2026
9 checks passed
@mihow
mihow deleted the fix/capture-set-picker-choices branch August 1, 2026 16:16
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.

2 participants