Skip to content

Conversation

@joshunrau
Copy link
Collaborator

@joshunrau joshunrau commented Dec 10, 2025

  • Add qr code to assignment slider
  • Add a new instrument record page where users can view the instrument summary again. In this case, all measures are shown, regardless of the visibility defined on the instrument. This can be accessed by clicking on a specific record in the "table" page for a specific subject in the data hub.
  • Change the order of tabs in the subject datahub pages so assignments are last and table is first.

closes #1247
closes #1246
closes #1245

Summary by CodeRabbit

  • New Features
    • GET endpoint to fetch an individual instrument record by ID
    • New record detail page showing instrument data, subject, and measurements
    • Click table rows to navigate directly to record details
    • QR code generation and display for assignments
    • Option to show all measures in instrument summaries
    • Route preloading to fetch record and subject data for faster rendering

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 10, 2025

Walkthrough

Adds API and frontend support for viewing individual instrument records: new GET /instrument-records/:id endpoint with access control, React query hooks, a record detail route with preloading, table navigation to records, QR code component for assignments, and InstrumentSummary option to show all measures.

Changes

Cohort / File(s) Summary
API — Instrument Record Retrieval
apps/api/src/instrument-records/instrument-records.controller.ts, apps/api/src/instrument-records/instrument-records.service.ts
Adds findById controller endpoint (GET /instrument-records/:id) and service method. Uses ValidObjectIdPipe, CurrentUser('ability'), accessibleQuery for read access and throws NotFoundException when record missing.
Frontend — QR Code & deps
apps/web/package.json, apps/web/src/components/QRCode.tsx
Adds qrcode and @types/qrcode deps. New QRCode React component renders a themed canvas QR code from a URL.
Frontend — Data fetching hooks
apps/web/src/hooks/useInstrumentRecordQuery.ts, apps/web/src/hooks/useSubjectQuery.ts, apps/web/src/hooks/useInstrumentVisualization.ts
New instrumentRecordQueryOptions/useInstrumentRecordQuery and subjectQueryOptions/useSubjectQuery using TanStack Query + Suspense. useInstrumentVisualization now adds __id__ to records and excludes it from export.
Frontend — Routes & navigation
apps/web/src/route-tree.ts, apps/web/src/routes/_app/datahub/$subjectId/$recordId.tsx, apps/web/src/routes/_app/datahub/$subjectId/table.tsx, apps/web/src/routes/_app/datahub/index.tsx
New record detail route /datahub/$subjectId/$recordId with loader prefetching record and subject. Table rows navigate to record detail via record.__id__. Adjusted top-level datahub navigation target.
Frontend — Assignments UI
apps/web/src/routes/_app/datahub/$subjectId/assignments.tsx
Integrates QRCode in assignment sheet, tightens sheet open condition to require assignment and instrument, and uses safe optional chaining for assignment URL handling.
UI Component & Exports
packages/react-core/src/components/InstrumentSummary/InstrumentSummary.tsx, packages/react-core/src/index.ts
Added optional displayAllMeasures?: boolean prop to InstrumentSummary and exported the component from the package index.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as Client UI (Router)
    participant Route as Record Detail Route
    participant Hooks as Query Hooks
    participant API as API Client
    participant Controller as InstrumentRecordsController
    participant Service as InstrumentRecordsService
    participant DB as Database

    User->>UI: Click table row (contains __id__)
    UI->>Route: Navigate to /datahub/$subjectId/$recordId
    Route->>Hooks: useInstrumentRecordQuery(recordId)
    Hooks->>API: GET /v1/instrument-records/:id
    API->>Controller: findById(id, ability)
    Controller->>Service: findById(id, { ability })
    Service->>DB: accessibleQuery + find by id
    DB-->>Service: InstrumentRecord (or null)
    Service-->>Controller: InstrumentRecord or NotFound
    Controller-->>API: Return record
    API-->>Hooks: Parse record ($InstrumentRecord.parse)
    Hooks-->>Route: Record resolved
    Route->>Hooks: useSubjectQuery(record.subjectId)
    Hooks->>API: GET /v1/subjects/:id
    API-->>Hooks: Subject data
    Hooks-->>Route: Subject resolved
    Route->>Route: Resolve instrument via useInstrument
    Route->>UI: Render InstrumentSummary(displayAllMeasures)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review access-control query in InstrumentRecordsService.findById to ensure correct ability usage.
  • Validate route loader prefetch logic in apps/web/src/routes/_app/datahub/$subjectId/$recordId.tsx handles missing records gracefully.
  • Check useInstrumentRecordQuery and useSubjectQuery keys and parsing ($InstrumentRecord, $Subject) for correctness.
  • Inspect QRCode canvas sizing and theme color choices for accessibility.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'New features' is vague and generic, lacking specificity about the actual changes made in this pull request. Use a more descriptive title that summarizes the main changes, such as 'Add instrument record page, QR codes, and reorder datahub tabs'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed All three linked issues are addressed: tab reordering (#1247), instrument summary re-viewing (#1246), and QR code generation (#1245).
Out of Scope Changes check ✅ Passed All changes align with the three linked issues; backend endpoint, React hooks, QR component, routing, and UI updates support the stated objectives.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
apps/api/src/instrument-records/instrument-records.service.ts (1)

257-265: findById access control and error handling look good; consider tightening the where-clause for consistency

The method correctly:

  • Applies accessibleQuery(ability, 'read', 'InstrumentRecord') so only readable records can be fetched.
  • Returns a 404 (NotFoundException) when the record is missing or inaccessible, which is a reasonable behavior.

For readability and consistency with other methods (e.g., count, find), you might want to fold the id into the same AND array instead of mixing top-level and AND filters:

-    const record = await this.instrumentRecordModel.findFirst({
-      where: { AND: [accessibleQuery(ability, 'read', 'InstrumentRecord')], id }
-    });
+    const record = await this.instrumentRecordModel.findFirst({
+      where: {
+        AND: [
+          accessibleQuery(ability, 'read', 'InstrumentRecord'),
+          { id }
+        ]
+      }
+    });

Optional, but makes the intent a bit clearer and mirrors existing patterns in this service.

apps/web/src/routes/_app/datahub/index.tsx (1)

175-178: Subject selection now correctly lands on the table view

Routing subject row clicks to ./${subject.id}/table matches the new “table-first” UX. You might also consider updating the lookup flow (Line 130) to navigate to the table instead of assignments for consistency, unless you intentionally want lookup to prioritize assignments.

apps/web/src/routes/_app/datahub/$subjectId/table.tsx (1)

10-66: Be explicit about subjectId when navigating to the record route

The row click navigation to /datahub/$subjectId/$recordId is correct conceptually, but you currently only pass recordId in params. To avoid relying on any implicit parent param inference, it’s safer to also pass subjectId explicitly:

-  const navigate = Route.useNavigate();
+  const navigate = Route.useNavigate();
@@
-        onEntryClick={(row) => {
-          void navigate({ params: { recordId: row.__id__ }, to: '/datahub/$subjectId/$recordId' });
-        }}
+        onEntryClick={(row) => {
+          void navigate({
+            to: '/datahub/$subjectId/$recordId',
+            params: { subjectId: params.subjectId, recordId: row.__id__ }
+          });
+        }}

This keeps the route params explicit and avoids subtle routing issues if the router stops inheriting parent params.

apps/web/src/routes/_app/datahub/$subjectId/assignments.tsx (1)

24-81: Tightening slider open condition is good; refine URL handling and QR fallback

Requiring assignment and instrument in the open condition is a solid improvement. Two small follow‑ups:

  • For the input, avoid passing a null/undefined URL directly; normalizing to an empty string is safer:
-              <Input readOnly className="h-9" id="link" value={assignment?.url} />
+              <Input readOnly className="h-9" id="link" value={assignment?.url ?? ''} />
  • For the QR code, encoding 'javascript:void(0)' when no URL is available is confusing for users scanning it. It’s cleaner to only render the QR code when a real URL exists:
-            <QRCode url={assignment?.url ?? 'javascript:void(0)'} />
+            {assignment?.url && <QRCode url={assignment.url} />}

This keeps the UI honest and avoids meaningless or odd QR targets.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1cdf215 and 6843f36.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • apps/api/src/instrument-records/instrument-records.controller.ts (1 hunks)
  • apps/api/src/instrument-records/instrument-records.service.ts (1 hunks)
  • apps/web/package.json (2 hunks)
  • apps/web/src/components/QRCode.tsx (1 hunks)
  • apps/web/src/hooks/useInstrumentRecordQuery.ts (1 hunks)
  • apps/web/src/hooks/useInstrumentVisualization.ts (2 hunks)
  • apps/web/src/hooks/useSubjectQuery.ts (1 hunks)
  • apps/web/src/route-tree.ts (10 hunks)
  • apps/web/src/routes/_app/datahub/$subjectId/$recordId.tsx (1 hunks)
  • apps/web/src/routes/_app/datahub/$subjectId/assignments.tsx (2 hunks)
  • apps/web/src/routes/_app/datahub/$subjectId/route.tsx (1 hunks)
  • apps/web/src/routes/_app/datahub/$subjectId/table.tsx (2 hunks)
  • apps/web/src/routes/_app/datahub/index.tsx (1 hunks)
  • packages/react-core/src/components/InstrumentSummary/InstrumentSummary.tsx (2 hunks)
  • packages/react-core/src/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
packages/react-core/src/components/InstrumentSummary/InstrumentSummary.tsx (2)
packages/runtime-core/src/types/instrument.core.ts (1)
  • AnyUnilingualInstrument (48-48)
packages/react-core/src/types.ts (1)
  • SubjectDisplayInfo (23-23)
apps/web/src/components/QRCode.tsx (2)
apps/outreach/src/scripts/theme-init.js (1)
  • theme (9-9)
packages/runtime-internal/src/interactive/worker.js (1)
  • url (61-61)
apps/web/src/hooks/useSubjectQuery.ts (1)
packages/schemas/src/subject/subject.ts (1)
  • $Subject (20-27)
apps/web/src/hooks/useInstrumentRecordQuery.ts (1)
packages/schemas/src/instrument-records/instrument-records.ts (1)
  • $InstrumentRecord (42-51)
apps/web/src/routes/_app/datahub/$subjectId/table.tsx (3)
apps/web/src/routes/_app/datahub/$subjectId/route.tsx (1)
  • Route (73-75)
apps/web/src/routes/_app/datahub/index.tsx (1)
  • Route (185-191)
apps/web/src/routes/_app/datahub/$subjectId/graph.tsx (1)
  • Route (168-170)
apps/web/src/routes/_app/datahub/$subjectId/$recordId.tsx (5)
apps/web/src/routes/_app/datahub/index.tsx (1)
  • Route (185-191)
apps/web/src/hooks/useInstrumentRecordQuery.ts (2)
  • useInstrumentRecordQuery (15-17)
  • instrumentRecordQueryOptions (5-13)
apps/web/src/hooks/useSubjectQuery.ts (2)
  • useSubjectQuery (19-21)
  • subjectQueryOptions (9-17)
apps/web/src/hooks/useInstrument.ts (1)
  • useInstrument (10-32)
packages/react-core/src/components/InstrumentSummary/InstrumentSummary.tsx (1)
  • InstrumentSummary (23-217)
apps/api/src/instrument-records/instrument-records.controller.ts (3)
apps/api/src/core/decorators/route-access.decorator.ts (1)
  • RouteAccess (18-20)
apps/web/src/store/types.ts (1)
  • CurrentUser (8-10)
apps/api/src/auth/auth.types.ts (1)
  • AppAbility (23-23)
apps/api/src/instrument-records/instrument-records.service.ts (2)
apps/api/src/core/types.ts (1)
  • EntityOperationOptions (3-5)
apps/api/src/auth/ability.utils.ts (1)
  • accessibleQuery (26-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint-and-test
🔇 Additional comments (11)
apps/web/src/hooks/useInstrumentVisualization.ts (3)

21-21: Type addition looks good.

The __id__ field follows the existing metadata naming convention.


80-80: Consider whether __id__ should be excluded from exports.

Line 80 omits __time__ but not __id__. The new __id__ field will appear in CSV/Excel/JSON exports. Verify this is intentional—record IDs may be useful for traceability but could clutter analytical datasets.


233-233: Verify record.id is always defined and typed as string.

Ensure the API guarantees record.id exists and is a string before assigning it to __id__.

apps/api/src/instrument-records/instrument-records.controller.ts (1)

96-101: GET /instrument-records/:id endpoint is wired correctly

The new route cleanly mirrors the existing patterns:

  • Uses ValidObjectIdPipe and @CurrentUser('ability') consistently.
  • Applies RouteAccess({ action: 'read', subject: 'InstrumentRecord' }) and delegates to the service method that also enforces accessibleQuery.

No issues from my side here.

apps/web/src/hooks/useInstrumentRecordQuery.ts (1)

1-17: Instrument record query hook looks correct

Query key, axios call, and $InstrumentRecord parsing are consistent and should integrate cleanly with suspense queries.

apps/web/package.json (1)

49-69: QR code dependencies align with new component usage

Adding qrcode and @types/qrcode matches the new QRCode component; nothing else stands out here. Please just ensure lockfile and builds/tests are updated and passing.

apps/web/src/routes/_app/datahub/$subjectId/route.tsx (1)

55-65: Tab ordering change matches requirements

Putting the table and graph tabs before assignments aligns with the requested DataHub tab order, with no functional regressions apparent.

packages/react-core/src/index.ts (1)

1-11: Publicly exporting InstrumentSummary is appropriate

Re‑exporting InstrumentSummary from the package index matches the existing pattern and allows clean consumption from the web app.

apps/web/src/components/QRCode.tsx (1)

1-32: QRCode canvas wrapper is straightforward and fits the use case

Hooking into useTheme and rendering via qrcode.toCanvas in an effect is a clean, focused implementation; nothing problematic stands out here.

packages/react-core/src/components/InstrumentSummary/InstrumentSummary.tsx (1)

17-17: LGTM! Clean implementation of the displayAllMeasures feature.

The optional prop is properly typed and the short-circuit logic correctly bypasses visibility filtering when enabled. This aligns with the PR objective to re-view instrument summaries with all measures visible.

Also applies to: 23-29, 38-40

apps/web/src/routes/_app/datahub/$subjectId/$recordId.tsx (1)

1-41: LGTM! Well-structured data flow.

The component correctly chains data fetching (record → subject → instrument) and the loader appropriately prefetches both instrumentRecord and subject data. The use of displayAllMeasures aligns with the PR objective to re-view completed instruments with all measures visible.

Comment on lines +9 to +17
export const subjectQueryOptions = ({ params }: { params: SubjectQueryParams }) => {
return queryOptions({
queryFn: async () => {
const response = await axios.get(`/v1/subjects/${params.id}`, { params });
return $Subject.parseAsync(response.data);
},
queryKey: ['subjects', `id-${params.id}`]
});
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove redundant params from axios config.

Line 12 passes params as query parameters, resulting in /v1/subjects/123?id=123. The ID is already in the URL path and shouldn't be duplicated as a query parameter.

Apply this diff:

 export const subjectQueryOptions = ({ params }: { params: SubjectQueryParams }) => {
   return queryOptions({
     queryFn: async () => {
-      const response = await axios.get(`/v1/subjects/${params.id}`, { params });
+      const response = await axios.get(`/v1/subjects/${params.id}`);
       return $Subject.parseAsync(response.data);
     },
     queryKey: ['subjects', `id-${params.id}`]
   });
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const subjectQueryOptions = ({ params }: { params: SubjectQueryParams }) => {
return queryOptions({
queryFn: async () => {
const response = await axios.get(`/v1/subjects/${params.id}`, { params });
return $Subject.parseAsync(response.data);
},
queryKey: ['subjects', `id-${params.id}`]
});
};
export const subjectQueryOptions = ({ params }: { params: SubjectQueryParams }) => {
return queryOptions({
queryFn: async () => {
const response = await axios.get(`/v1/subjects/${params.id}`);
return $Subject.parseAsync(response.data);
},
queryKey: ['subjects', `id-${params.id}`]
});
};
🤖 Prompt for AI Agents
In apps/web/src/hooks/useSubjectQuery.ts around lines 9 to 17, the axios.get
call incorrectly passes the entire params object as query parameters causing
duplicate id in the URL (e.g. /v1/subjects/123?id=123); remove the second
argument ({ params }) from axios.get so the request uses only the path
parameter, leaving the axios call as axios.get(`/v1/subjects/${params.id}`) and
keep the rest (parsing and queryKey) unchanged.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/web/src/hooks/useInstrumentVisualization.ts (2)

18-23: New required __id__ field may be a breaking type change

Making __id__ a required property on InstrumentVisualizationRecord will force all manual constructions of this type (tests, mocks, ad‑hoc objects) to supply an ID. If there are any such call sites, this will surface as TS errors.

Two options you might consider:

  • Keep it required but add a short doc comment clarifying it’s the backing instrument‑record ID used for navigation and not included in exports.
  • Or, make it optional (__id__?: string) if you expect partially populated records in other contexts.

80-80: Confirm intent to strip __id__ from all export formats

exportRecords now omits both __time__ and __id__, so the internal record identifier is never present in CSV/TSV/JSON/Excel outputs. That’s a reasonable privacy/cleanliness choice and matches the server‑side export shape that doesn’t include record IDs, but it does mean downstream consumers cannot link exported rows back to specific instrument records.

Please confirm this is the desired behavior for consumers of JSON/CSV exports; if not, you may want to keep __id__ for JSON only, or expose a more explicit recordId field.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6843f36 and 861d4c2.

📒 Files selected for processing (1)
  • apps/web/src/hooks/useInstrumentVisualization.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/hooks/useInstrumentVisualization.ts (2)
apps/api/src/instrument-records/instrument-records.controller.ts (1)
  • exportRecords (70-72)
apps/api/src/instrument-records/instrument-records.service.ts (1)
  • exportRecords (135-221)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint-and-test
🔇 Additional comments (1)
apps/web/src/hooks/useInstrumentVisualization.ts (1)

225-238: Populating __id__ from record.id looks correct

Using record.id to set __id__ at materialization time cleanly ties visualization rows back to their underlying instrument records and keeps the rest of the shaping logic unchanged.

Assuming record.id is always a defined string, this is sound and aligns with the new type.

@joshunrau joshunrau merged commit e0305a0 into DouglasNeuroInformatics:main Dec 10, 2025
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

1 participant