Skip to content

feat(website): add SeqSet citations section to admin dashboard#6769

Open
theosanderson-agent wants to merge 8 commits into
mainfrom
feat/admin-seqset-citations
Open

feat(website): add SeqSet citations section to admin dashboard#6769
theosanderson-agent wants to merge 8 commits into
mainfrom
feat/admin-seqset-citations

Conversation

@theosanderson-agent

@theosanderson-agent theosanderson-agent commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a SeqSet citations section to the admin dashboard that lists every publication citing a SeqSet in the instance. This gives super users a single place to see all external citations across all SeqSets, complementing the existing per-SeqSet (get-seqset-citations) and per-sequence (get-sequence-citations) views, which previously had no aggregate/admin-level counterpart.

It also lets super users manually register a citation ("curated", as opposed to "crossref"-discovered) — useful when a publication hasn't been indexed by CrossRef yet, and as a way to seed citation data for e2e testing without depending on an external CrossRef call.

Backend

  • New endpoint GET /admin/get-all-seqset-citations on SeqSetCitationsController.
    • Placing it under /admin/* means it is automatically gated to super users by the existing SecurityConfig matcher (auth.requestMatchers("/admin/*").hasAuthority(SUPER_USER)), so no new security wiring is needed.
    • It stays on SeqSetCitationsController, which is @ConditionalOnProperty(ENABLE_SEQSETS), so the endpoint only exists when SeqSets are enabled.
  • SeqSetCitationsDatabaseService.getAllSeqSetCitations() joins the citation source, join, and SeqSets tables and returns each citation source together with the list of SeqSets it references (accession version, name and DOI). Results are grouped per citation source and ordered by year (descending), mirroring the existing getSequenceCitations query shape.
  • New API types AdminSeqSetCitation and CitedSeqSet.
  • New POST /admin/add-seqset-citation and DELETE /admin/delete-seqset-citation endpoints (also under /admin/*, so also super-user gated):
    • Adding sets origin = CURATED on seqset_citation_source (the schema already had a CROSSREF/CURATED enum, but nothing ever wrote CURATED before this). Re-submitting the same sourceDOI updates its metadata and links any newly listed SeqSets without dropping existing links.
    • Deleting only works for CURATED citations — CROSSREF-discovered citations can't be removed through this admin path.
    • AdminSeqSetCitation now also returns origin, so the frontend knows which rows are deletable.

Website

  • BackendClient.getAllSeqSetCitations(token) calls the new endpoint (parsed with the new adminSeqSetCitations zod schema).
  • New AdminSeqSetCitationsTable component (renamed from SeqSetCitationsTable for clarity, since it's specifically the admin aggregate view) renders, per citation: the title (linking to the source DOI), contributors, year, and the cited SeqSets (each linking to its SeqSet page, with accession version and DOI), plus a "Remove" button on CURATED rows.
  • New AddSeqSetCitationForm renders a form (source DOI, title, year, contributors, cited SeqSet accession-versions) that posts to the new add endpoint.
    • It also has a "Fetch from DOI" button that calls CrossRef's public REST API (api.crossref.org/works/{doi}) directly from the browser to autopopulate title, year, and contributors. That endpoint sends Access-Control-Allow-Origin: *, so no backend proxy is needed for this lookup (unlike the institutional CrossRef deposit/cited-by API the backend already talks to, which needs credentials).
  • New AdminSeqSetCitationsSection is a small client island that owns the citation list state so add/remove feel immediate, without a full page reload.
  • admin/dashboard.astro fetches and renders the new section below the pipeline statistics. The fetch is only issued when seqSetsAreEnabled(), and the section degrades gracefully on error.

Tests

  • Backend endpoint tests in CitationEndpointsTest:
    • returns 401/403 without auth / for a non-super user (for get, add, and delete),
    • returns an empty list when there are no citations,
    • returns each citation with its cited SeqSet metadata (accession version, name, DOI) after a simulated CrossRef run,
    • adding a citation for an existing SeqSet sets origin = CURATED and shows up in both the admin and per-SeqSet citation views,
    • adding a citation for a non-existent SeqSet returns 404,
    • re-adding the same sourceDOI with an additional SeqSet updates metadata and links both SeqSets,
    • deleting a curated citation removes it; deleting a CROSSREF-origin citation is rejected; deleting an unknown DOI returns 404.
  • New Playwright spec admin-citations.dependent.spec.ts: logs in as the super user, creates a SeqSet, adds a citation through the admin UI, asserts it renders, deletes it via the UI, asserts it disappears.

Verification

  • ./gradlew ktlintFormat and the full backend test suite (./gradlew test, 72 test classes) pass, real Postgres via Testcontainers.
  • Website npm run check-types, npm run format, and CI=1 npm run test pass (683 unit tests).
  • Manually exercised the full flow on the preview deployment as the seeded super user, including after the review-feedback fixes: created a SeqSet, added a curated citation (via "Fetch from DOI" autofill against a real CrossRef DOI), confirmed it appears in the admin table (now above the form), the SeqSet's own citations dialog, and the cited sequence's "Cited in" section, then removed it via the admin UI and confirmed it disappears everywhere. Also confirmed the Year-validation fix rejects a blank year instead of silently submitting 0. Screenshots below.

Admin dashboard: citations table above the add-citation form

Admin dashboard, empty citations table above the form

Autofilling from a DOI via CrossRef

Typing a DOI and clicking "Fetch from DOI" populates title, year, and contributors from CrossRef's public API:

DOI autofill populated the form

Citation appears in the table (above the form), with real CrossRef metadata and a Remove button

Citation added, table above form

Citation also appears on the SeqSet's own citations dialog, and on the cited sequence's page

SeqSet page citations dialog

Sequence page Cited in section

🤖 Generated with Claude Code

🚀 Preview: https://feat-admin-seqset-citatio.loculus.org

Adds an admin-only view listing all publications that cite any SeqSet in
the instance, alongside the existing pipeline statistics.

Backend:
- New `GET /admin/get-all-seqset-citations` endpoint on the
  SeqSetCitationsController. It lives under `/admin/*`, so it is gated to
  super users by the existing security config, and remains behind the
  `ENABLE_SEQSETS` conditional like the rest of the controller.
- `SeqSetCitationsDatabaseService.getAllSeqSetCitations()` returns each
  citation source together with the SeqSets it references (accession
  version, name and DOI), grouped per citation source and ordered by year.
- New `AdminSeqSetCitation` / `CitedSeqSet` API types.

Website:
- `BackendClient.getAllSeqSetCitations()` calls the new endpoint.
- New `SeqSetCitationsTable` component renders citations with links to the
  source DOI and to each cited SeqSet page.
- The admin dashboard fetches and renders the section when SeqSets are
  enabled.

Tests:
- Backend endpoint tests cover auth (unauthorized / forbidden for
  non-super users), the empty case, and the populated case asserting the
  cited SeqSet metadata.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@claude claude Bot added website Tasks related to the web application backend related to the loculus backend component labels Jun 25, 2026
@tombch tombch self-requested a review June 29, 2026 08:14
Super users can now register that a publication or other source cites a
SeqSet without waiting for CrossRef to discover it, via new
POST /admin/add-seqset-citation and DELETE /admin/delete-seqset-citation
endpoints. These set/require origin=CURATED on seqset_citation_source,
distinct from CrossRef-discovered (origin=CROSSREF) citations, which
cannot be deleted through this path.

The admin dashboard's SeqSet citations section gained a form to add
citations and a remove button on manually curated rows. Renamed the
existing read-only table/section components to AdminSeqSetCitations*
for clarity.

This also unblocks e2e coverage of the citations feature, which
previously had no way to seed data without an external CrossRef call.
Added a Playwright spec that logs in as the super user, creates a
SeqSet, adds and removes a citation via the admin UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@theosanderson-agent theosanderson-agent added the preview Triggers a deployment to argocd label Jun 30, 2026
theosanderson-agent and others added 2 commits June 30, 2026 23:47
…tions spec

CI caught a race: createSeqSet() returns as soon as the Save click fires,
before the client redirects to /seqsets/{id}.{version}. The test then read
page.url() too early and got the list page, so accession parsing failed.
Wait for the URL to match the detail route first, like expectDetailLayout
already does in seqset.page.ts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add a "Fetch from DOI" button next to the source DOI field on the
manual-citation admin form. It calls CrossRef's public REST API
(api.crossref.org/works/{doi}) directly from the browser -- that
endpoint sends Access-Control-Allow-Origin: *, so no backend proxy is
needed -- and fills in title, year, and contributors from the response.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@theosanderson theosanderson marked this pull request as ready for review June 30, 2026 22:56
@claude

claude Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @theosanderson's task in 6m 13s —— View job


Code review

Todo list

  • Gather context (PR diff, files, AGENTS.md guidelines)
  • Review backend changes (Kotlin: controller, service, API types)
  • Review website changes (React/Astro components, backend client, types)
  • Review integration tests
  • Post inline comments for actionable issues
  • Post final summary review

Summary

This is a clean, well-scoped addition. Reusing the existing /admin/* super-user matcher in SecurityConfig for the three new endpoints is the right call — confirmed the matcher (auth.requestMatchers("/admin/*").hasAuthority(SUPER_USER)) does correctly cover all three new single-segment paths. Confirmed seqset_to_citation_source has ON DELETE CASCADE to seqset_citation_source, so deleteCuratedCitation doesn't leave orphaned join rows. addCuratedCitation/deleteCuratedCitation run inside the class-level @Transactional SeqSetCitationsDatabaseService, so the upsert + link-insert is atomic. Backend test coverage of the new endpoints (auth, empty list, full round-trip, re-add/merge, 404/422 paths) is thorough.

One actionable bug found, left as an inline comment:

  • website/src/components/AdminDashboard/AddSeqSetCitationForm.tsx (around line 131): the Year field is marked required, but Number('') is 0, and 0 passes Number.isInteger. A blank/whitespace-only Year silently submits year: 0 instead of showing the validation message.

Minor, non-blocking observations (not worth separate inline comments):

  • Neither the frontend nor addCuratedCitation validates year bounds (e.g., rejects 0/negative) beyond "is an integer" — low risk since this is super-user-only, but worth keeping in mind once the Year bug above is fixed.
  • AdminSeqSetCitationsTable's onDelete prop is optional, but it's always passed by the single call site (AdminSeqSetCitationsSection) — could be made required, purely a readability nit.
  • No website unit tests were added for the new components, but that matches the existing convention for this directory (e.g. PipelineStatisticsTable also has no unit test) — coverage instead comes from the new Playwright spec, which looks solid.

@theosanderson

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89cbdbe646

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread website/src/components/AdminDashboard/AddSeqSetCitationForm.tsx
Reads better with existing citations listed first and the add form
last, and means a newly added citation appears right above the form
that added it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread website/src/components/AdminDashboard/AddSeqSetCitationForm.tsx Outdated
theosanderson-agent and others added 3 commits July 1, 2026 00:12
- Reject manually adding/updating a citation source whose DOI was
  already discovered via CrossRef, instead of silently flipping its
  origin to CURATED (which would have made it deletable through the
  admin-only manual-citation path). Per review discussion, reject
  rather than just preserving the existing origin.
- addCuratedCitation now returns the full set of SeqSets linked to the
  citation source after the upsert, not just the ones in the current
  request, so re-adding a known DOI with one new SeqSet no longer
  drops previously linked SeqSets from the response.
- EndpointTestExtension's per-test DB cleanup never truncated
  seqset_citation_source / seqset_to_citation_source, so citation rows
  leaked across test methods within a run. This was latent before
  (upsert silently overwrote origin) but surfaced real failures once
  CROSSREF-origin protection was added, since some tests reused the
  same literal source DOI. Fixed the truncate statement.
- AddSeqSetCitationForm: disable inputs/textareas until client
  hydration completes, per AGENTS.md's flaky-Playwright-test guidance.
- Fix Year validation: an empty Year field coerced to 0 via Number('')
  and incorrectly passed Number.isInteger(0), letting the form submit
  with year: 0 instead of failing validation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
table-auto let the Year and Cited SeqSets columns get squeezed by long
citation titles. Switch to table-fixed with a colgroup so Citation,
Year, Cited SeqSets, and the Remove column each get a stable share of
the width, and wrap long content instead of crushing neighbors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ations

# Conflicts:
#	backend/src/main/kotlin/org/loculus/backend/service/seqsetcitations/SeqSetCitationsDatabaseService.kt
#	backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/CitationEndpointsTest.kt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend related to the loculus backend component preview Triggers a deployment to argocd website Tasks related to the web application

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants