Repository navigation
Start-session page downloads every subject in the group, with personal information, to list custom IDs #1536
Description
Current Implementation and Limitations
The start-session component (start-session.tsx#L33) and its route loader (#L124) both use subjectsQueryOptions({ params: { groupId } }). They share one query key, so the page makes one GET /v1/subjects?groupId= request. SubjectsService.find returns every subject in the group, with no pagination and no field selection (subjects.service.ts#L144). Each subject includes name, date of birth, sex and groupIds.
The page uses this list only to build customSubjectIds (start-session.tsx#L35-L37). These are the IDs of the subjects for which isSubjectWithPersonalInfo is false, that is, subjects with at least one of firstName, lastName, dateOfBirth or sex missing (subject-utils index.ts#L42-L50). The page never shows the personal information.
This causes three problems:
- Size. Each subject is about 313 bytes with personal information and about 236 bytes without. A group with 15,000 subjects returns about 4.5 MB on each load. API responses are not compressed: the
/api/*block in theCaddyfilehas noencode(Caddyfile#L4-L6).useSuspenseQuerysets a minimumstaleTimeof 1 second, andrefetchOnWindowFocusis on. Thus the list refetches on each visit, and each time the tab becomes visible again. - Data minimization. Each visit sends the names and dates of birth of all subjects in the group to the browser. A subject with a custom ID can also have a date of birth and sex, and the response sends them too. The page needs none of this information.
- No group.
groupIdis optional (subjects.controller.ts#L38-L46). An admin with no current group sends nogroupId, and gets every subject in the instance.
Related, but not in the scope of this issue: the datahub loads the same full list with the same query key (datahub/index.tsx#L481, #L509), and a second list with hasRecord (#L343-L345). A fix for the start-session page does not change the datahub.
Associated Application Components
Client, Server
Proposed Solution
Add an endpoint that returns only the IDs that the page needs. Filter in the MongoDB query, not in JavaScript. The response is then a list of short strings with no personal information.
- Path.
SubjectsControllerhas@Get(':id')(subjects.controller.ts#L49), soapps/api/AGENTS.mdasks for a path with more than one segment. For example, useGET /v1/subjects/groups/:groupId/custom-ids. A requiredgroupIdalso removes problem 3: an admin with no current group gets no suggestions. - Filter. For each of the four fields, match
nullor{ isSet: false }. On MongoDB, a Prisma filter fornulldoes not match a document where the field is missing.InstrumentsService.finduses the same pattern forseriesGroupId(instruments.service.ts#L291-L295). - Scope. Put
accessibleQuery(ability, 'read', 'Subject')and{ groupIds: { has: groupId } }in thewhere, and have the controller forward@CurrentUser('ability'). ValidategroupIdwithValidObjectIdPipe. - Response. Use
select: { id: true }.
If the custom-ID list itself can grow large, change the field to a type-ahead search with a result limit.
Related: PR #1426 moves a similar subject-ID computation into the database.
Tests. Unit tests for the new service method: it excludes a subject with full personal information, it includes a subject whose field is null and a subject whose field is missing, and it excludes subjects of other groups. An e2e test for the same exclusion and group scoping. The e2e test that the start-session page suggests custom IDs already exists (testing/src/specs/start-session.spec.ts), and it must continue to pass.
Estimated Difficulty
Medium
Priority
Medium
Activity