Commit 906d1a7
feat(website): add SeqSet citations section to admin dashboard (#6769)
## 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

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

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

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


🤖 Generated with [Claude Code](https://claude.com/claude-code)
🚀 Preview: https://feat-admin-seqset-citatio.loculus.org
---------
Co-authored-by: theosanderson-agent <theo@theo.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: theosanderson-agent <sndrsn@gmail.com>1 parent 32562de commit 906d1a7
17 files changed
Lines changed: 1171 additions & 58 deletions
File tree
- backend/src
- main/kotlin/org/loculus/backend
- api
- controller
- service/seqsetcitations
- test/kotlin/org/loculus/backend/controller
- seqsetcitations
- integration-tests/tests
- pages
- specs/features
- utils
- website/src
- components/AdminDashboard
- pages/admin
- services
- types
Lines changed: 14 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
115 | 115 | | |
116 | 116 | | |
117 | 117 | | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
118 | 132 | | |
119 | 133 | | |
120 | 134 | | |
| |||
Lines changed: 20 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
6 | 8 | | |
7 | 9 | | |
8 | 10 | | |
| |||
101 | 103 | | |
102 | 104 | | |
103 | 105 | | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
104 | 124 | | |
105 | 125 | | |
106 | 126 | | |
| |||
Lines changed: 131 additions & 22 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| 28 | + | |
| 29 | + | |
27 | 30 | | |
28 | 31 | | |
29 | 32 | | |
| |||
59 | 62 | | |
60 | 63 | | |
61 | 64 | | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
62 | 75 | | |
63 | 76 | | |
64 | 77 | | |
| |||
211 | 224 | | |
212 | 225 | | |
213 | 226 | | |
214 | | - | |
215 | | - | |
216 | | - | |
217 | | - | |
218 | | - | |
219 | | - | |
220 | | - | |
221 | | - | |
222 | | - | |
223 | | - | |
224 | | - | |
| 227 | + | |
225 | 228 | | |
226 | 229 | | |
227 | 230 | | |
| |||
276 | 279 | | |
277 | 280 | | |
278 | 281 | | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
288 | | - | |
289 | | - | |
| 282 | + | |
290 | 283 | | |
291 | 284 | | |
292 | 285 | | |
| |||
515 | 508 | | |
516 | 509 | | |
517 | 510 | | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
518 | 627 | | |
519 | 628 | | |
520 | 629 | | |
| |||
Lines changed: 3 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
244 | 244 | | |
245 | 245 | | |
246 | 246 | | |
| 247 | + | |
| 248 | + | |
247 | 249 | | |
248 | 250 | | |
249 | 251 | | |
| |||
253 | 255 | | |
254 | 256 | | |
255 | 257 | | |
| 258 | + | |
256 | 259 | | |
257 | 260 | | |
258 | 261 | | |
| |||
0 commit comments