feat: add sorting options to person list page #223
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
The person list page only supports searching by name. There's no way to sort persons by relevance metrics, making it hard to discover the most prominent or recently active people in the archive.
Proposal
Add a sort dropdown to the person list page (similar to the document search sort). Possible sort options:
Open questions
PersonSummaryDTO(which already has document count) cover most of this?🧑💻 Felix Brandt — Fullstack Developer
Implementation decisions for the person list sort feature — scope, approach, and API contract.
Resolved
1. Sort implementation approach
In-memory sort in
PersonService.findAll()using aComparator. The repository stays untouched — both native queries already return all required data, and the person list is small enough that DB-level sorting is unnecessary. No new query methods needed.2. "Last document date" — cut from v1
Would require a new
lastDocumentDatefield onPersonSummaryDTOand a non-trivialMAX(document_date)subquery across both native queries, plus a TypeScript type regen. Not worth the complexity for v1.3. "Recently updated" — cut from v1
Cut alongside item 2. Can be revisited in a follow-up issue.
4. Sort + search combined
Sort and search always work together —
?q=opa&sort=DOCUMENT_COUNT&dir=DESCis fully supported. With in-memory sorting this is free:findAll(q)returns the filtered list, the comparator is applied after regardless of whetherqis set. Sort dropdown stays visible and active while a search query is in use.5. API contract
GET /api/persons?sort=NAME|DOCUMENT_COUNT&dir=ASC|DESCsort=NAME&dir=ASCdirpattern exactlyv1 scope summary
Clean, minimal change: one new service comparator, one new
sort/dirquery param pair, frontend sort dropdown wired to URL params.🧑💻 Felix Brandt — Senior Fullstack Developer
Questions & Observations
Comparator placement — The sort logic shouldn't live inline in
PersonService.findAll(). A switch over 4 cases inside a service method pushes it past 20 lines fast. Suggest extracting to a package-privatePersonSortComparatorclass or a static factory:PersonSortComparator.by(sort, dir). The service method stays thin.Enum location —
PersonSort { NAME, DOCUMENT_COUNT }is part of the API contract. It belongs indto/PersonSort.java, not defined inside the controller. Same for aSortDirectionenum if one doesn't already exist.Service signature —
PersonService.findAll(String q, PersonSort sort, SortDirection dir)is clear. The default (NAME ASC) should be expressed in the service (or the enum itself), not only viarequired = falsein the controller — keeps the default in one place.Frontend init pattern — The sort and dir state should use
untrack(() => data.filters.sort)on init, same assenderIdandsortDirdo in briefwechsel. Don't bind directly todata.TypeScript types — After regen,
sortanddirwill bestringin the generated client. Consider narrowing to a literal union ('NAME' | 'DOCUMENT_COUNT') in a local type alias for the frontend, so the compiler catches typos ingoto()calls.Suggestions
should_sort_by_name_ascending,should_sort_by_name_descending,should_sort_by_document_count_descending,should_sort_by_document_count_ascending. No Spring context needed.DOCUMENT_COUNTcomparator — when two persons have equal counts, fall back to name alphabetically. Otherwise sort is non-deterministic on re-renders.🏛️ Markus Keller — Application Architect
Questions & Observations
Layer compliance — looks good — Controller accepts params → passes to service → service fetches from repo → applies comparator in memory. Nothing bleeds across boundaries. The sort decision in the discussion comment is architecturally correct.
Enum ownership —
PersonSortis an API contract type. It belongs indto/, not anonymous inside the controller or service. If a future second endpoint (e.g. a stats endpoint) also wants to sort persons, the enum is already in a shared, importable location.In-memory sort ceiling — The discussion notes this is acceptable for the expected data volume. For the record: a family archive reaching even 2,000 persons would still sort in well under a millisecond in Java. Revisiting the approach would only become relevant above ~50,000 rows, which is not a realistic concern. The simplicity payoff is worth it.
Default sort as a named constant — The
NAME ASCdefault is currently implicit (controllerrequired = false, service fallback). Consider making it an explicit constant:This prevents silent divergence if the controller default is updated but the service fallback isn't (or vice versa).
Suggestions
PersonController.getPersons()signature growing to three params (q,sort,dir) is still thin. No concern there — it delegates immediately, which is correct.🧪 Sara Holt — QA Engineer
Questions & Observations
The issue has no explicit acceptance criteria. Before implementation starts, I'd want these defined:
sort=DOCUMENT_COUNT&dir=DESC, the person with the highest document count appears firstq=opa&sort=DOCUMENT_COUNT&dir=DESC, results are filtered AND sorted (search + sort combined)sortparam is absent, results default toNAME ASC— identical to current behaviourEdge cases not addressed in the issue:
documentCount. The sort is non-deterministic without a secondary key. Should be name A–Z.DOCUMENT_COUNT DESC, persons with 0 documents should appear last (not mixed in arbitrarily). This should be an explicit test case.GET /api/persons?sort=INVALIDreturn? IfPersonSortis a Java enum, Spring returns 400 automatically. That behaviour should be tested and documented.q=zzzznothing&sort=DOCUMENT_COUNTshould return an empty list, not an error.Suggestions
PersonSortComparatorin isolation for all four combinations — no Spring context, fast feedback.goto()with the correct params.GET /api/persons?sort=DOCUMENT_COUNT&dir=DESCwith seeded data — assert the response order, not just the status code.🔐 Nora Steiner — Application Security
Questions & Observations
This feature is low-risk — read-only, no new data exposure, no auth model changes. A few points worth confirming during implementation:
Enum validation on
sortparam — IfPersonSortis a Java enum and Spring binds it directly via@RequestParam, Spring returns a400 Bad Requeston unrecognized values automatically. This is the correct behaviour and should be tested. If the binding is done manually (e.g.String sort→ manual lookup), add explicit validation and a400response before the service call.dirparam validation — Same principle. Accept onlyASCandDIR(or use aSortDirectionenum). Don't let arbitrary strings pass through, even if they're not used in SQL. The in-memory sort means there's no SQL injection vector here, but accepting unvalidated input is a pattern that erodes over time.No new data exposure —
PersonSummaryDTOis unchanged. The sort changes the order, not the content.documentCountis already in the response — no new sensitive fields.Endpoint authorization —
GET /api/personshas no@RequirePermissiontoday, relying on global authentication. That's consistent with the rest of the read endpoints. No change needed for this feature.Suggestions
GET /api/persons?sort=GARBAGE— assert400, not500or a silently defaulted200. This confirms the enum binding is working as expected and doesn't silently swallow bad input.🎨 Leonie Voss — UI/UX Design Lead
Questions & Observations
Header crowding on mobile (320px) — The current header row already has a search input (
w-56) and a "New person" button. Adding a sort dropdown as a third control on the same row will overflow on narrow screens. Proposed layout:[Sort ▼] [Dir ↑↓] [Search ________] [+ New Person]— all in one rowSort direction control — The briefwechsel page uses a small toggle button for direction. Reuse that pattern here for consistency — a sort dropdown for the field, an icon button (↑/↓ or A–Z/Z–A label) for direction. Don't put both in a single
<select>with options like "Name A–Z" and "Name Z–A" separately — that doubles the option count and hides the relationship.Accessibility — The sort
<select>needs a visible<label>or at minimumaria-label="Sortierung". An icon-only direction toggle needsaria-labelwith the current direction state, e.g.aria-label="Aufsteigend sortieren".i18n keys needed — At minimum:
persons_sort_label("Sortieren nach")persons_sort_name("Name")persons_sort_document_count("Anzahl Dokumente")URL persistence — Sort and dir in the URL (
?sort=DOCUMENT_COUNT&dir=DESC) means deep links and browser back button work correctly. Good.Suggestions
⚙️ Tobias Wendt — DevOps & Platform
No infrastructure concerns from my angle. Confirming what I checked:
db/migration/.sortanddirare query params handled entirely in application code.One forward-looking note: the in-memory sort approach is the right call for the current data volume. If the archive ever grew to tens of thousands of persons, a DB-side
ORDER BYwould be more efficient. Not a concern for a family archive, but worth keeping in mind if the codebase is ever adapted for a different use case.