Skip to content

Implement 'Has record' filter into datahub#1329

Open
david-roper wants to merge 8 commits intoDouglasNeuroInformatics:mainfrom
david-roper:has-record-filter
Open

Implement 'Has record' filter into datahub#1329
david-roper wants to merge 8 commits intoDouglasNeuroInformatics:mainfrom
david-roper:has-record-filter

Conversation

@david-roper
Copy link
Copy Markdown
Collaborator

@david-roper david-roper commented Apr 1, 2026

Adds a filter to display subjects that have records only

closes issue #1282

Summary by CodeRabbit

  • New Features
    • Added a "Subjects with records" checkbox to the Datahub table to show only subjects that have instrument records (default: off).
    • Subject identifiers in the table are now consistently truncated per the subject ID display setting for a stable layout.
    • The checkbox is synced with the table filter state so it reflects and updates the active subject filter.

@david-roper david-roper requested a review from joshunrau as a code owner April 1, 2026 20:52
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a "Subjects with records" filter checkbox, computes scoped subject IDs with useInstrumentRecords(), and applies a HasRecordFilter to the subjectId column (accessor/cell/filter adjusted). Also sets a default { subjectId: { hasRecords: false } } column filter.

Changes

Cohort / File(s) Summary
Datahub table & filters
apps/web/src/routes/_app/datahub/index.tsx
Add useInstrumentRecords() usage to derive idsWithRecords; introduce HasRecordFilter and a "Subjects with records" checkbox wired to subjectId column filter; change subjectId accessor/cell to return scoped ID and slice only in cell; add filterFn to enforce hasRecords logic; set initial columnFilters to include { subjectId: { hasRecords: false } }.

Sequence Diagram(s)

sequenceDiagram
    participant UI as "Filters UI\n(Checkbox)"
    participant Table as "MasterDataTable\n(DataTable / Columns)"
    participant Hook as "useInstrumentRecords\n(records store)"
    UI->>Table: toggle "Subjects with records"
    Table->>Hook: request instrument records (compute idsWithRecords)
    Hook-->>Table: return idsWithRecords
    Table->>Table: apply subjectId column filter { hasRecords: true/false }
    Table-->>UI: reflect checkbox state
    Table-->>User: render rows filtered by idsWithRecords when enabled
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • joshunrau
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implement 'Has record' filter into datahub' accurately describes the main change: adding a filter to display subjects with records only, as confirmed by the PR description and file changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/routes/_app/datahub/index.tsx`:
- Around line 337-345: idsWithRecords is undefined while the query loads and
it's built from a sliced display prefix causing false positives; eagerly
initialize a canonical lookup (e.g., const idsWithRecords = new Set<string>())
before using the query, populate that Set from full unscoped IDs returned by
useInstrumentRecords() by calling removeSubjectIdScope(record.subjectId) (do NOT
slice here), and use that Set for hasRecords checks; keep any
.slice(subjectIdDisplaySetting ?? 9) truncation only inside the cell renderer
that displays the subject ID so lookup uses the full ID and never reads .length
on undefined.
- Around line 353-363: The subject column's filter now mixes a plain string and
the HasRecordFilter shape causing filterFn (in the subject column) to receive a
string so filter.hasRecords is undefined; update all places that set the subject
filter (the code that currently writes a plain string around subjectId) to
instead store a stable object shape { query: <subjectId|string>, hasRecords:
<boolean> } (or maintain the checkbox state separately and use that when
building the filter object), and ensure the column's filterFn and any references
to HasRecordFilter (the filterFn, HasRecordFilter type usage, and the subjectId
filter setters) consistently read/write that shape so filter.hasRecords is
defined and the subject search continues to work.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7e505b7f-2d79-41b3-ac61-00140042753e

📥 Commits

Reviewing files that changed from the base of the PR and between c51defd and 02b9ae5.

📒 Files selected for processing (1)
  • apps/web/src/routes/_app/datahub/index.tsx

Copy link
Copy Markdown
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

♻️ Duplicate comments (3)
apps/web/src/routes/_app/datahub/index.tsx (3)

437-440: ⚠️ Potential issue | 🔴 Critical

Search overwrites the filter object, losing hasRecords state.

setFilterValue(value) replaces the object with a string. Merge the query into the existing filter shape instead.

Proposed fix
 onSearchChange={(value, table) => {
   const subjectIdColumn = table.getColumn('subjectId')!;
-  subjectIdColumn.setFilterValue(value);
+  subjectIdColumn.setFilterValue((prev: HasRecordFilter): HasRecordFilter => ({
+    ...prev,
+    query: value
+  }));
 }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/_app/datahub/index.tsx` around lines 437 - 440, The
current onSearchChange handler calls subjectIdColumn.setFilterValue(value) which
overwrites the filter object (losing hasRecords); instead read the existing
filter via subjectIdColumn.getFilterValue(), merge it with the new
subjectId/search value and then call
subjectIdColumn.setFilterValue(mergedFilter) so existing keys like hasRecords
are preserved; update the handler that references onSearchChange,
table.getColumn('subjectId'), and subjectIdColumn.setFilterValue/getFilterValue
to perform a shallow merge of the existing filter object with the new subjectId
value before setting.

353-363: ⚠️ Potential issue | 🔴 Critical

filterFn doesn't implement text search filtering.

When the search box is used (line 439), the filter value becomes a string, causing filter.hasRecords to be undefined. The function returns true for all rows, effectively disabling search. The filterFn must handle both the hasRecords checkbox and the search query.

Proposed fix (after extending HasRecordFilter type)
 filterFn: (row, id, filter: HasRecordFilter) => {
   const value = row.getValue(id);
   if (!value) {
     return false;
   }
-  if (filter.hasRecords && idsWithRecords && idsWithRecords.length > 0) {
-    return idsWithRecords.includes(value as string);
-  } else {
-    return true;
+  // Check hasRecords filter
+  if (filter.hasRecords && idsWithRecords.size > 0) {
+    if (!idsWithRecords.has(value as string)) {
+      return false;
+    }
   }
+  // Check search query
+  if (filter.query) {
+    return (value as string).toLowerCase().includes(filter.query.toLowerCase());
+  }
+  return true;
 },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/_app/datahub/index.tsx` around lines 353 - 363, The
filterFn for the column currently only checks filter.hasRecords and ignores a
string search query, so when the search box supplies a string (making
filter.hasRecords undefined) every row passes; update the filterFn in
apps/web/src/routes/_app/datahub/index.tsx to handle both cases by checking the
runtime type of filter (HasRecordFilter | string): if filter is an object with
hasRecords, use idsWithRecords.includes(row.getValue(id) as string) to filter;
if filter is a string, perform a text match against the cell value
(row.getValue(id)) using a case-insensitive contains check; ensure you reference
HasRecordFilter, filterFn, idsWithRecords and row.getValue(id) when making the
change so both checkbox and search query are supported.

38-40: ⚠️ Potential issue | 🟠 Major

HasRecordFilter type needs a query field to support search.

The onSearchChange handler (line 439) sets the filter value to a plain string. This overwrites the { hasRecords } object, causing filter.hasRecords to become undefined in the filterFn. Additionally, the filterFn doesn't filter by search text.

Extend the type and update all usages accordingly:

Proposed type change
 type HasRecordFilter = {
   hasRecords: boolean;
+  query: string;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/_app/datahub/index.tsx` around lines 38 - 40, The
HasRecordFilter type lacks a query field, so onSearchChange is overwriting the
filter object with a string and breaking filterFn; update the HasRecordFilter
type to include query: string, then update all places that construct or read
this filter (notably the onSearchChange handler and any setFilter calls) to
set/merge both hasRecords and query (e.g., setFilter(prev => ({ ...prev, query:
newQuery }))), and update filterFn to check both filter.hasRecords and
filter.query (perform text matching against the item when filter.query is
non-empty) so filtering supports both record existence and search text.
🧹 Nitpick comments (1)
apps/web/src/routes/_app/datahub/index.tsx (1)

55-56: Variable name shadows the type.

HasRecordFilter uses PascalCase like the type. Rename to hasRecordFilter for clarity.

Proposed fix
   const subjectColumn = columns.find((column) => column.id === 'subjectId')!;
-  const HasRecordFilter = subjectColumn.getFilterValue() as HasRecordFilter;
+  const hasRecordFilter = subjectColumn.getFilterValue() as HasRecordFilter;

Then update line 169:

-            checked={HasRecordFilter.hasRecords}
+            checked={hasRecordFilter.hasRecords}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/_app/datahub/index.tsx` around lines 55 - 56, The local
variable name `HasRecordFilter` shadows the type name; rename the variable to
`hasRecordFilter` where it's assigned from subjectColumn.getFilterValue()
(currently const HasRecordFilter = subjectColumn.getFilterValue() as
HasRecordFilter) and update all subsequent uses (e.g., the later reference that
was suggested to change at the other location) to use `hasRecordFilter` so the
type name `HasRecordFilter` remains only as the type annotation and the value
variable uses camelCase.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/routes/_app/datahub/index.tsx`:
- Around line 409-414: The initial filter entry with id 'subjectId' sets value
to { hasRecords: false } but HasRecordFilter now includes a query field; update
the initial filter state for the 'subjectId' entry (the object under id:
'subjectId' in apps/web/src/routes/_app/datahub/index.tsx) to include query
(e.g., query: '' or appropriate default) so the value satisfies the extended
HasRecordFilter type.

---

Duplicate comments:
In `@apps/web/src/routes/_app/datahub/index.tsx`:
- Around line 437-440: The current onSearchChange handler calls
subjectIdColumn.setFilterValue(value) which overwrites the filter object (losing
hasRecords); instead read the existing filter via
subjectIdColumn.getFilterValue(), merge it with the new subjectId/search value
and then call subjectIdColumn.setFilterValue(mergedFilter) so existing keys like
hasRecords are preserved; update the handler that references onSearchChange,
table.getColumn('subjectId'), and subjectIdColumn.setFilterValue/getFilterValue
to perform a shallow merge of the existing filter object with the new subjectId
value before setting.
- Around line 353-363: The filterFn for the column currently only checks
filter.hasRecords and ignores a string search query, so when the search box
supplies a string (making filter.hasRecords undefined) every row passes; update
the filterFn in apps/web/src/routes/_app/datahub/index.tsx to handle both cases
by checking the runtime type of filter (HasRecordFilter | string): if filter is
an object with hasRecords, use idsWithRecords.includes(row.getValue(id) as
string) to filter; if filter is a string, perform a text match against the cell
value (row.getValue(id)) using a case-insensitive contains check; ensure you
reference HasRecordFilter, filterFn, idsWithRecords and row.getValue(id) when
making the change so both checkbox and search query are supported.
- Around line 38-40: The HasRecordFilter type lacks a query field, so
onSearchChange is overwriting the filter object with a string and breaking
filterFn; update the HasRecordFilter type to include query: string, then update
all places that construct or read this filter (notably the onSearchChange
handler and any setFilter calls) to set/merge both hasRecords and query (e.g.,
setFilter(prev => ({ ...prev, query: newQuery }))), and update filterFn to check
both filter.hasRecords and filter.query (perform text matching against the item
when filter.query is non-empty) so filtering supports both record existence and
search text.

---

Nitpick comments:
In `@apps/web/src/routes/_app/datahub/index.tsx`:
- Around line 55-56: The local variable name `HasRecordFilter` shadows the type
name; rename the variable to `hasRecordFilter` where it's assigned from
subjectColumn.getFilterValue() (currently const HasRecordFilter =
subjectColumn.getFilterValue() as HasRecordFilter) and update all subsequent
uses (e.g., the later reference that was suggested to change at the other
location) to use `hasRecordFilter` so the type name `HasRecordFilter` remains
only as the type annotation and the value variable uses camelCase.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ea23aa28-a4ff-4503-be2c-a9d646cd5bc6

📥 Commits

Reviewing files that changed from the base of the PR and between 02b9ae5 and 67fb4af.

📒 Files selected for processing (1)
  • apps/web/src/routes/_app/datahub/index.tsx

Copy link
Copy Markdown
Collaborator

@joshunrau joshunrau left a comment

Choose a reason for hiding this comment

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

please review the code rabbit comments

Copy link
Copy Markdown
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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/routes/_app/datahub/index.tsx (1)

437-440: ⚠️ Potential issue | 🔴 Critical

Search overwrites filter with plain string—breaks checkbox state.

setFilterValue(value) replaces the entire filter object with a string, losing hasRecords. Merge into the existing filter shape instead.

Proposed fix
 onSearchChange={(value, table) => {
   const subjectIdColumn = table.getColumn('subjectId')!;
-  subjectIdColumn.setFilterValue(value);
+  subjectIdColumn.setFilterValue((prev: HasRecordFilter): HasRecordFilter => ({
+    ...prev,
+    query: value
+  }));
 }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/_app/datahub/index.tsx` around lines 437 - 440, The
onSearchChange handler currently calls subjectIdColumn.setFilterValue(value)
which replaces the filter object with a plain string and drops the existing
hasRecords flag; change this to read the current filter object from
subjectIdColumn.getFilterValue(), merge the new search value into that object
(preserving hasRecords and any other keys), and then call
subjectIdColumn.setFilterValue(mergedFilter) so the checkbox state (hasRecords)
is retained; update the onSearchChange callback where subjectIdColumn and
setFilterValue are used to perform this merge.
♻️ Duplicate comments (2)
apps/web/src/routes/_app/datahub/index.tsx (2)

409-414: ⚠️ Potential issue | 🟠 Major

Add query to initial filter state.

After extending HasRecordFilter, the initial value must include query.

,

Proposed fix
 {
   id: 'subjectId',
   value: {
-    hasRecords: false
+    hasRecords: false,
+    query: ''
   } satisfies HasRecordFilter
 },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/_app/datahub/index.tsx` around lines 409 - 414, The
initial filter entry with id 'subjectId' uses a value typed as HasRecordFilter
but is missing the required query property; update the value object in the
initial filter state used by the datahub route so that the value includes query
(e.g., query: '' or appropriate initial query value) alongside hasRecords,
ensuring it satisfies HasRecordFilter and keeps the id 'subjectId' intact.

337-345: ⚠️ Potential issue | 🟠 Major

Truncated IDs and undefined state cause incorrect filtering.

Two issues remain from prior feedback:

  1. idsWithRecords is undefined until the query resolves—if filter.hasRecords is toggled during loading, comparison logic may behave unexpectedly.
  2. Slicing IDs to display length (subjectIdDisplaySetting ?? 9) for the lookup means subjects with different full IDs but identical prefixes will incorrectly match.

Initialize idsWithRecords eagerly as an empty array/Set, and use full unscoped IDs for the lookup. Keep truncation only in the cell renderer.

,

Proposed fix
  const records = useInstrumentRecords();
- let idsWithRecords: string[];
- if (records.data) {
-   idsWithRecords = [
-     ...new Set(
-       records.data.map((record) => removeSubjectIdScope(record.subjectId).slice(0, subjectIdDisplaySetting ?? 9))
-     )
-   ];
- }
+ const idsWithRecords = new Set<string>(
+   records.data?.map((record) => removeSubjectIdScope(record.subjectId)) ?? []
+ );

Then update the filterFn to use full unscoped ID and Set.has():

  filterFn: (row, id, filter: HasRecordFilter) => {
-   const value = row.getValue(id);
-   if (!value) {
-     return false;
-   }
-   if (filter.hasRecords && idsWithRecords && idsWithRecords.length > 0) {
-     return idsWithRecords.includes(value as string);
-   } else {
-     return true;
-   }
+   const fullId = removeSubjectIdScope(row.original.id);
+   if (filter.hasRecords && idsWithRecords.size > 0) {
+     return idsWithRecords.has(fullId);
+   }
+   return true;
  },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/_app/datahub/index.tsx` around lines 337 - 345,
idsWithRecords is left undefined until useInstrumentRecords resolves and you're
truncating IDs for lookup; initialize idsWithRecords eagerly (e.g., const
idsWithRecords = new Set<string>() or an empty array) so toggle logic using
filter.hasRecords is safe during loading, populate it when records.data exists
by adding full unscoped IDs from removeSubjectIdScope(record.subjectId) (do NOT
slice by subjectIdDisplaySetting), and update the filterFn to check membership
with Set.has(fullUnscopedId) (or array includes) while keeping any truncation
(subjectIdDisplaySetting ?? 9) only inside the cell renderer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/routes/_app/datahub/index.tsx`:
- Around line 38-40: The HasRecordFilter type currently only has hasRecords and
needs a query field to hold the search string; update the type declaration
(HasRecordFilter) to include query: string, then update any
initializations/usages (e.g., where filter state is created) so the state
includes an empty string for query, and ensure onSearchChange assigns the
incoming string to that query property; this will keep filterFn's input types
consistent with the string assignment in onSearchChange.
- Around line 352-363: The filterFn for the subject column currently treats its
filter param as HasRecordFilter but onSearchChange passes a plain string, so
searches are ignored; update the column's filter contract and implementation
(accessorFn, filterFn) to accept a unified shape or normalize the incoming
filter (e.g., if typeof filter === 'string' convert to { query: filter }) and
then apply both checks: 1) if query exists, compare it against the accessor
value (using removeSubjectIdScope(subject.id).slice(...)) to filter by search,
and 2) if hasRecords and idsWithRecords provided, ensure the value is included
in idsWithRecords; reference the existing symbols accessorFn, filterFn,
removeSubjectIdScope, subjectIdDisplaySetting, onSearchChange, and
idsWithRecords when making the change.

---

Outside diff comments:
In `@apps/web/src/routes/_app/datahub/index.tsx`:
- Around line 437-440: The onSearchChange handler currently calls
subjectIdColumn.setFilterValue(value) which replaces the filter object with a
plain string and drops the existing hasRecords flag; change this to read the
current filter object from subjectIdColumn.getFilterValue(), merge the new
search value into that object (preserving hasRecords and any other keys), and
then call subjectIdColumn.setFilterValue(mergedFilter) so the checkbox state
(hasRecords) is retained; update the onSearchChange callback where
subjectIdColumn and setFilterValue are used to perform this merge.

---

Duplicate comments:
In `@apps/web/src/routes/_app/datahub/index.tsx`:
- Around line 409-414: The initial filter entry with id 'subjectId' uses a value
typed as HasRecordFilter but is missing the required query property; update the
value object in the initial filter state used by the datahub route so that the
value includes query (e.g., query: '' or appropriate initial query value)
alongside hasRecords, ensuring it satisfies HasRecordFilter and keeps the id
'subjectId' intact.
- Around line 337-345: idsWithRecords is left undefined until
useInstrumentRecords resolves and you're truncating IDs for lookup; initialize
idsWithRecords eagerly (e.g., const idsWithRecords = new Set<string>() or an
empty array) so toggle logic using filter.hasRecords is safe during loading,
populate it when records.data exists by adding full unscoped IDs from
removeSubjectIdScope(record.subjectId) (do NOT slice by
subjectIdDisplaySetting), and update the filterFn to check membership with
Set.has(fullUnscopedId) (or array includes) while keeping any truncation
(subjectIdDisplaySetting ?? 9) only inside the cell renderer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b102a1a5-57bb-4591-be02-dff461a15c6c

📥 Commits

Reviewing files that changed from the base of the PR and between 67fb4af and c1ded9b.

📒 Files selected for processing (1)
  • apps/web/src/routes/_app/datahub/index.tsx

Copy link
Copy Markdown
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

♻️ Duplicate comments (1)
apps/web/src/routes/_app/datahub/index.tsx (1)

38-40: ⚠️ Potential issue | 🟠 Major

Keep subjectId on one filter shape.

Line 440 still writes a plain string into subjectId, but Lines 355-364 read that value as HasRecordFilter. After the first search, the subject query stops filtering rows and the checkbox state gets overwritten. Store { query, hasRecords } end-to-end.

Proposed fix
 type HasRecordFilter = {
   hasRecords: boolean;
+  query: string;
 };
@@
             filterFn: (row, id, filter: HasRecordFilter) => {
-              const value = row.getValue(id);
-              if (!value) {
-                return false;
-              }
-              if (filter.hasRecords) {
-                return idsWithRecords.has(value as string);
-              }
+              const value = (row.getValue(id) as string | undefined) ?? '';
+              if (filter.query && !value.toLowerCase().includes(filter.query.toLowerCase())) {
+                return false;
+              }
+              if (filter.hasRecords && !idsWithRecords.has(value)) {
+                return false;
+              }
               return true;
             },
@@
               id: 'subjectId',
               value: {
-                hasRecords: false
+                hasRecords: false,
+                query: ''
               } satisfies HasRecordFilter
             },
@@
         onSearchChange={(value, table) => {
           const subjectIdColumn = table.getColumn('subjectId')!;
-          subjectIdColumn.setFilterValue(value);
+          subjectIdColumn.setFilterValue((prevValue: HasRecordFilter): HasRecordFilter => {
+            return {
+              ...prevValue,
+              query: value
+            };
+          });
         }}

Also applies to: 355-364, 410-415, 438-440

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/routes/_app/datahub/index.tsx` around lines 38 - 40, The current
filter flow stores a plain string into subjectId but parts of the code expect a
HasRecordFilter shape ({ hasRecords: boolean }), causing the checkbox state to
be lost; update the subject filter storage so subjectId always holds an object
like { query: string, hasRecords: boolean } end-to-end: update the setter(s)
that write to subjectId to store { query, hasRecords } (e.g., where setSubjectId
or the subject filter value is assigned), and update readers that assume a
string to read .query and .hasRecords (e.g., the components/handlers that read
subjectId to build queries and the checkbox state logic), and adjust any
types/interfaces so HasRecordFilter includes query: string to keep type-safety
across functions that reference subjectId and HasRecordFilter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/routes/_app/datahub/index.tsx`:
- Line 25: The call to useInstrumentRecords() is missing a group scope and
therefore fetches records across all groups, causing removeSubjectIdScope() to
let unscoped subject IDs from different groups collide in the idsWithRecords
Set; update the call site (where useInstrumentRecords is invoked) to pass
groupId: currentGroup?.id so the hook only returns records for the current
group, matching the pattern used in dashboard.tsx (lines 33–38).

---

Duplicate comments:
In `@apps/web/src/routes/_app/datahub/index.tsx`:
- Around line 38-40: The current filter flow stores a plain string into
subjectId but parts of the code expect a HasRecordFilter shape ({ hasRecords:
boolean }), causing the checkbox state to be lost; update the subject filter
storage so subjectId always holds an object like { query: string, hasRecords:
boolean } end-to-end: update the setter(s) that write to subjectId to store {
query, hasRecords } (e.g., where setSubjectId or the subject filter value is
assigned), and update readers that assume a string to read .query and
.hasRecords (e.g., the components/handlers that read subjectId to build queries
and the checkbox state logic), and adjust any types/interfaces so
HasRecordFilter includes query: string to keep type-safety across functions that
reference subjectId and HasRecordFilter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9d7aefed-3d7c-4416-a3c0-0ebdf38a27b6

📥 Commits

Reviewing files that changed from the base of the PR and between c1ded9b and beeb043.

📒 Files selected for processing (1)
  • apps/web/src/routes/_app/datahub/index.tsx

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a “Subjects with records” filter to the Data Hub subjects table so users can restrict the list to subjects that have instrument records.

Changes:

  • Introduces a new checkbox filter (“With records only”) wired into the table’s column filtering state.
  • Fetches instrument records for the active group and derives a subject-id set to support the filter.
  • Adjusts subject ID cell rendering to consistently truncate based on the subject ID display setting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +338 to +348
const records = useInstrumentRecords({
params: {
groupId: currentGroup?.id
}
});
const idsWithRecords = new Set<string>();
if (records.data) {
records.data.forEach((record) => {
idsWithRecords.add(removeSubjectIdScope(record.subjectId));
});
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

useInstrumentRecords fetches full instrument record objects for the entire group to compute idsWithRecords. If records include large payloads, this can be very expensive in the Data Hub list view and scale poorly with data volume. If possible, prefer a lightweight API/query for “subjectIds with at least one record” (or a server-side aggregated endpoint), and memoize the derived Set so it isn’t rebuilt on every render.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@joshunrau performance issue here which may require more extensive backend addition?

@david-roper david-roper requested a review from joshunrau April 7, 2026 19:29
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.

4 participants