Skip to content

Grey out custom id subject DOB and sex if already filled, let admins see default group ids - #1591

Merged
joshunrau merged 13 commits into
DouglasNeuroInformatics:mainfrom
david-roper:grey-custom-session-info
Oct 1, 2026
Merged

joshunrau merged 13 commits into
DouglasNeuroInformatics:mainfrom
david-roper:grey-custom-session-info

Conversation

@david-roper

Copy link
Copy Markdown
Collaborator

Summary

Two improvements to the Custom Identifier field on the Start Session page.

1. Admins with no group now see custom ID suggestions

An admin who belongs to no group has no current group, so the custom ID combobox used to skip the lookup and list nothing.

When there is no current group, the form now scopes a custom ID to the default group (root$<id>). The combobox now suggests exactly those subjects: every subject whose ID carries the root$ scope and that was identified by a custom ID rather than personal information.

  • New endpoint GET /v1/subjects/default-group/custom-ids
    • Gated by @RouteAccess({ action: 'read', subject: 'Subject' }), the same as the per-group custom-ID route.
    • Filtered with accessibleQuery, so an admin sees every such subject and a group manager sees only those in their own groups.
    • Returns IDs only, like the per-group route.
    • Two path segments, so the existing GET /v1/subjects/:id route can't catch it.
  • Matches on the ID's scope, not on groupIds. A later session, upload or assignment in a group adds that group to the subject but leaves its root$ ID unchanged. Matching on "no groups" would hide those subjects, even though they are exactly the ones the form resolves to.
  • Prisma workaround. Prisma's MongoDB connector compiles startsWith into an unescaped regex, so the $ in root$ acts as an end anchor and matches nothing. The query uses a lexical range instead (gte: 'root$', lt: 'root%'), through a small idsWithPrefix helper.
  • The "identified by custom ID" condition is now one shared IDENTIFIED_BY_CUSTOM_ID clause, used by both the per-group and default-group lookups.
  • useSubjectCustomIdsQuery calls the new endpoint when there is no current group, instead of returning [] without a request.

2. Existing subjects' date of birth and sex are filled in and locked

When the custom ID picked or typed matches an existing subject, the form fills in the date of birth and sex that subject already records, and disables those fields. This shows the clinician they don't need to enter them again.

  • Fetched on selection, one subject at a time. The suggestion list stays IDs-only, so no one's date of birth or sex is sent to the browser until that subject is chosen. The new useSubjectDemographicsLookup hook calls the existing GET /v1/subjects/:id, which is already permission-scoped.
    • A 404 means the ID is new. It resolves to null and raises no error notification.
    • Only dateOfBirth and sex are parsed, through a new $SubjectDemographics schema in packages/schemas.
  • Typing an existing ID in full behaves the same as picking it. The lookup only runs for IDs present in the suggestion list, so typing doesn't send a request per keystroke.
  • Looked up by the ID the form will submit (scoped to the current group, or root$), so the details shown belong to the subject the session will actually use.
  • Only recorded values are locked. A subject with a sex but no date of birth locks the sex and leaves the date of birth editable.
  • Changing or clearing the ID clears and unlocks the filled-in fields, so a new subject never inherits the previous one's details. Switching to Personal Information does the same.
  • An older, slower lookup can't overwrite the result for the ID now selected.
  • Locked values are still submitted with the session.

Changes

Area Files
API apps/api/src/subjects/subjects.controller.ts, subjects.service.ts
Schemas packages/schemas/src/subject/subject.ts ($SubjectDemographics)
Web apps/web/src/hooks/useSubjectCustomIdsQuery.ts, apps/web/src/hooks/useSubjectDemographicsLookup.ts (new), apps/web/src/components/StartSessionForm/StartSessionForm.tsx
Unit tests apps/api/src/subjects/__tests__/subjects.service.spec.ts, apps/web/src/hooks/__tests__/useSubjectCustomIdsQuery.test.ts, apps/web/src/hooks/__tests__/useSubjectDemographicsLookup.test.ts (new), apps/web/src/__tests__/start-session-form.test.tsx
E2E testing/src/specs/start-session.spec.ts, testing/src/pages/_app/session/start-session.page.ts, testing/src/support/api-client.ts

Test notes

  • StartSessionPage gains dateOfBirthField, sexField and sexTrigger locators. sexTrigger is the visible dropdown. The element named subjectSex is Radix's hidden native <select>, which is never disabled, so it can't be used for enabled/disabled checks.
  • fillSessionDetails is split so fillSessionTiming() can fill the session type and date on its own.
  • The existing test "should start a session for a subject chosen from the existing custom identifiers" now fills only the session timing. Its subject's date of birth and sex are filled in and locked, which is the new intended behaviour.
  • start-session-form.test.tsx now renders inside a QueryClientProvider with axios mocked.

Test plan

  • pnpm lint passes for api, web, schemas and testing
  • Unit tests pass: web, schemas and api (1050 tests), plus the API integration suite
  • E2E: start-session.spec.ts and datahub.spec.ts pass in Chromium (33 tests)
  • New UI tests fail with the fix reverted, so they test the change
  • Full pnpm test:e2e. Locally, a 12-worker run hits the API rate limiter (429 Too Many Requests), unrelated to this change. CI runs one worker.
  • Manual: as an admin with no group, start a session, pick an existing root$ subject, and check that its date of birth and sex are filled in and greyed out. Then type a new ID and check they clear and unlock.

🤖 Generated with Claude Code

Closes Issue #1588
Closes Issue #1584

@david-roper
david-roper force-pushed the grey-custom-session-info branch from 9304a2d to 96d43cb Compare September 30, 2026 20:46
@joshunrau
joshunrau merged commit 58e7565 into DouglasNeuroInformatics:main Oct 1, 2026
2 checks passed
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