Skip to content

Filter occurrences by a saved set, and save selected occurrences as a set - #1545

Draft
mihow wants to merge 4 commits into
feat/occurrence-set-apifrom
feat/occurrence-set-ui
Draft

mihow wants to merge 4 commits into
feat/occurrence-set-apifrom
feat/occurrence-set-ui

Conversation

@mihow

@mihow mihow commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This is for people reviewing occurrences who want to come back to the same group of them later, such as a handful of tricky identifications to discuss or a small batch to check by hand. On the occurrence list they can now select rows and save them as a named occurrence set, and they can narrow the list to the occurrences in any set of the project.

Saving works on the selection, and a selection covers only the page being viewed (20 rows by default), so one set made this way holds at most a page of occurrences. That fits hand-picked lists. Sets for evaluation or retraining runs will usually need far more occurrences than that, and the way to make those is to save everything that matches the current filters, which is planned in #1539. Until then, larger sets can be created through the API from a list of occurrence ids.

It is the user-facing part of the occurrence sets epic #763. @mohamedelabbas1996 built it in #1492; this PR carries his UI over onto the smaller backend PR it is stacked on, with the contract changes from the review (the filter parameter is occurrence_set_id, and only curators who may create a set are offered the button).

List of Changes

Change (effect) How Notes
1. The occurrence filter panel can narrow the list to one occurrence set. New OccurrenceSetFilter (an EntityPicker over occurrences/sets/choices) registered for the occurrence_set_id field; shown under "More filters" beside the capture set. The "More filters" section opens on arrival when a set is already applied, as it does for the other filters there.
2. An occurrence_set_id present in another view's URL is carried along when following a link into the occurrence list. occurrence_set_id added to FILTERS_TO_OCCURRENCES. The species views do not offer the filter themselves yet; this only keeps the parameter when it is already there.
3. With rows selected, a "Create set" button in the toolbar beside Export saves them as a new set. CreateOccurrenceSetPopover + CreateOccurrenceSet form (name only) + useCreateOccurrenceSet mutation posting name, project_id, occurrence_ids to occurrences/sets/. Only the selected rows on the current page are saved; the help text says so. Not in the bulk identification bar: saving a set changes no occurrence, so it does not depend on update rights over them.
4. When the server refuses a new set, the form says why. getFormErrors (create-occurrence-set/form-errors.ts) puts a name error under the name field and the occurrence_ids or project_id errors in the form banner; Save stays disabled for a blank or whitespace-only name. Covered by form-errors.test.ts, using the error payloads the API returns.
5. The button is shown only to people allowed to create a set in the project. Reads user_permissions from the set choices response, which includes create when the caller has create_occurrenceset on the project (or is a superuser). Basic members and non-members do not see a button the server would refuse.
6. Typed payload and translated copy. ServerOccurrenceSet interface in data-services/models/occurrence-set.ts; new strings in utils/language.ts.

Detailed Description

Filter. The field name is the query parameter, occurrence_set_id, matching taxa_list_id and the backend filter added in the base PR. In #1492 the UI field was occurrence_set; the review settled on occurrence_set_id. The picker uses the same choices endpoint pattern as the capture set filter (names only, most recently updated first, up to 100).

The occurrence list still applies the project's default filters when a set is selected, so a set can show fewer rows than it holds. Turning off the default filters shows every occurrence in the set. That is the backend's documented behaviour in the base PR; this PR does not change it.

Creating a set. The form asks only for a name. The help text says how many of the selected occurrences on this page are being saved, and that changing the filters later does not change the set. It does not promise the contents never change: sets that people edit by hand are planned (#1538), and deleting or merging an occurrence removes it from every set (#1540). After a successful save, the popover closes and the set choices are refreshed, so the new set is immediately available in the filter.

When the server refuses the request, the reason is shown rather than axios's "Request failed with status code 400". The API reports validation failures per field: a blank name returns {"name": ["This field may not be blank."]}, which appears under the name field, and an occurrence deleted since the page loaded returns {"occurrence_ids": ["Not occurrences in project N: [...]"]}, which appears in the form's banner. A detail message (for example a 403) also goes in the banner. The shared parseServerError helper is unchanged; the mapping lives in a small module next to the form.

Selection is per page. The occurrence list keeps only the selected ids that are on the current page, and the default page size is 20, so changing page drops the selection. This PR does not change that, because it is how the bulk identification actions already behave. Saving all occurrences that match the current filters, as a one-time snapshot or a live query, is #1539, and is what evaluation and retraining sets will need.

Placement. The button sits in the toolbar beside Export and appears only while rows are selected, since that is the only time it does anything. It is deliberately not part of the bulk identification bar at the bottom of the page, so it does not interfere with the agree / suggest ID actions there, and those actions keep their own update-permission gate.

Permission gate. The button reads user_permissions from GET occurrences/sets/choices/?project_id=…. The base PR pins that response in TestListingSets.test_choices_offer_create_only_to_users_who_may_create_a_set (ami/main/test_occurrence_sets.py): it lists create for the project owner, an ML data manager and a superuser, and nothing for a basic member, a non-member or an anonymous visitor. A basic member's create request is refused with 403 (TestCreatingASet.test_only_users_with_the_create_permission_can_create_one).

For downstream branches

@mohamedelabbas1996, feat/evaluate-classifiers (#1493), feat/retrain-job-plumbing (#1501) and feat/retrain-training-set (#1502) each carry #1492's ten UI files unchanged. Rebasing them onto this PR gives add/add conflicts in those files. The simplest route is to drop #1492's UI commits when rebasing rather than resolve the conflicts; this PR replaces them. The differences from #1492's UI:

  • The filter field is occurrence_set_id (it was occurrence_set).
  • STRING.CREATE_OCCURRENCE_SET is removed; the button uses STRING.CREATE_SET.
  • The help text says later filter changes do not change the set, instead of "Its contents do not change afterwards". The string key is MESSAGE_CREATE_OCCURRENCE_SET, and the tooltip describes what a set is for.

STRING.FIELD_LABEL_OCCURRENCE_SET, API_ROUTES.OCCURRENCE_SETS and API_ROUTES.OCCURRENCE_SET_CHOICES keep their names, so the training form on feat/retrain-job-form (#1504), which uses them, should not need changes. Its occurrence_set parameter goes to that branch's training summary endpoint, not to the occurrence list, so the rename here does not touch it.

How to Test

Checks run on this branch (measured):

  • yarn tsc --noEmit: clean
  • yarn lint: clean
  • prettier --check on the changed files: clean
  • yarn test (jest): 15 suites, 77 tests passed (including the new form-errors.test.ts)
  • Base PR backend tests: python manage.py test ami.main.test_occurrence_sets ran 31 tests, OK

Not done for this PR: a manual run in the browser against the base branch's backend. To try it by hand:

  1. Run the stack on this branch and open a project's occurrence list as a project owner or curator.
  2. Select a few rows. A "Create set" button appears beside Export. Enter a name and save. Only the rows selected on the current page are saved (at most 20 with the default page size); moving to another page clears the selection.
  3. Open "Create set" again and enter only spaces: Save stays disabled.
  4. Open "More filters", pick the new set under "Occurrence set". The list narrows to the saved occurrences (turn off default filters to see all of them).
  5. Clear the selection: the button disappears. Log in as a basic member: the button does not appear when rows are selected.
  6. Open a link with ?occurrence_set_id=<id>: the filter is shown, applied and clearable.

Stacked on

This PR is stacked on feat/occurrence-set-api (the occurrence set API and the occurrence_set_id list filter), which is itself stacked on feat/occurrence-set-model. Retarget to main once those merge.

Related Issues

Closes #1535
Part of #763
Split from #1492
Follow-ups: #1539 (save the current filters as a set), #1540 (membership when occurrences are deleted or merged)

🤖 Generated with Claude Code

https://claude.ai/code/session_01MeUoKCfkiZaZjhja7e6AGh

mihow and others added 2 commits October 20, 2026 15:01
Someone reviewing an occurrence set needs to see what is in it, and the
occurrence list is where that review starts. The filter panel now offers an
occurrence set under More filters, beside the capture set, picked from the
set choices endpoint in the same way. The section opens on arrival when a set
is already applied, so a link carrying occurrence_set_id shows the filter
where it can be cleared.

The field is occurrence_set_id, the query parameter the occurrence list
backend reads, and it is part of the filters other views may carry into the
occurrence list.

Co-Authored-By: mohamedelabbas1996 <hack1996man@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MeUoKCfkiZaZjhja7e6AGh
Someone who has filtered the occurrence list down to the rows they care about
had no way to keep that selection. While rows are selected, a "Create set"
button now sits in the toolbar beside Export. It opens a small form that takes
a name and saves the selected occurrences on the current page as a new set in
the project. Selections do not carry across pages, so one set holds at most a
page of occurrences; the help text says so.

The button lives in the toolbar rather than in the bulk identification bar.
Saving a set changes none of the occurrences, so it should not depend on
update rights over them, and the selection bar stays reserved for
identification actions. The button appears only for callers the server
allows to create a set in this project, read from the user_permissions of the
set choices response, so a basic member is not offered an action that would
be refused.

The request sends project_id and occurrence_ids as integers, matching the
create contract of the occurrence set endpoint. When the server refuses the
request, the form shows its reason: an error on the name appears under the
name field, and errors on the occurrences or the project (for example an
occurrence deleted since the page loaded) appear in the form's banner,
instead of axios's "Request failed with status code 400". Save stays
disabled until the name has a non-blank character, and the name is sent
trimmed.

Co-Authored-By: mohamedelabbas1996 <hack1996man@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MeUoKCfkiZaZjhja7e6AGh
@mihow mihow added frontend occurrence-sets Saved lists of occurrences: create, filter, export, and score or train against them labels Oct 20, 2026
@netlify

netlify Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for antenna-preview ready!

Name Link
🔨 Latest commit a5b621a
🔍 Latest deploy log https://app.netlify.com/projects/antenna-preview/deploys/6ad93b4dc76eae13506e26d9
😎 Deploy Preview https://deploy-preview-1545--antenna-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 57 (🔴 down 8 from production)
Accessibility: 82 (🔴 down 7 from production)
Best Practices: 92 (🔴 down 8 from production)
SEO: 92 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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 and others added 2 commits October 21, 2026 15:21
The create popover and the filter tooltip described every set as a list
that never changes. They now say what a set is for (reviewing,
exporting and scoring the same occurrences again) and that later filter
changes do not change a saved set, which stays true when curated and
saved-filter sets arrive (#1538, #1539).

Co-Authored-By: mohamedelabbas1996 <hack1996man@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MeUoKCfkiZaZjhja7e6AGh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontend occurrence-sets Saved lists of occurrences: create, filter, export, and score or train against them

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant