Skip to content

Conversation

@david-roper
Copy link
Collaborator

@david-roper david-roper commented Dec 5, 2025

Working on issue #1230
Also works on issue #1247

  • Adds a new search bar which matches ids to the search string
  • matched ids are displayed in datahub table
  • export feature now can export ids that matched the search
  • ids and lookup now navigates to subject table page instead of assignment page
  • Added search bar and its info in the walkthrough
  • made the subject lookup as separate button.

Summary by CodeRabbit

  • New Features

    • Header search bar for real-time subject filtering in the Datahub table.
    • Subject lookup moved to a dedicated dialog opened by a search button.
  • Improvements

    • Exports (CSV, Excel, JSON) now export only the currently filtered subjects and validate non-empty selections.
    • More specific export notifications and propagated error messages.
    • Subject selection now navigates to the subject table view.
  • Documentation

    • Walkthrough updated with a new step and localized text for the header search bar and lookup button.
  • Style

    • Spotlight selector updated to target the renamed subject search bar.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

Walkthrough

Adds a header SearchBar and client-side subject filtering on the Datahub page; uses filtered subjects for CSV/Excel/JSON exports with non-empty validation and improved notifications; moves subject lookup trigger to a header button and opens IdentificationForm in a dialog; subject navigation now targets .//table.

Changes

Cohort / File(s) Summary
Datahub page: search, filtering & export
apps/web/src/routes/_app/datahub/index.tsx
Adds searchString and tableData state plus a useEffect to sync/filter tableData (stripping root$) when searchString or data change. MasterDataTable uses tableData. Export handlers derive listedSubjects from tableData, validate non-empty results, show an info notification before export, and propagate specific error messages. Replaces inline dialog SearchBar with a header SearchBar; Dialog.Trigger now renders a Button with UserSearchIcon. Subject lookup navigation updated to ./<id>/table.
Walkthrough steps update
apps/web/src/providers/WalkthroughProvider.tsx
Updated an existing walkthrough step to target #datahub-subject-search-bar and guide typing in the header search; added a new step targeting [data-spotlight-type="subject-lookup-search-button"] describing clicking the Subject Lookup button and entering a query; localized en/fr strings updated.
Style selector rename
apps/web/src/styles.css
Renamed spotlighted input selector from #subject-lookup-search-bar[data-spotlight='true'] > input to #datahub-subject-search-bar[data-spotlight='true'] > input; no other style changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Header as Header\n(SearchBar)
    participant LookupBtn as Subject\nLookup Button
    participant Dialog as Subject\nLookup Dialog
    participant Table as MasterDataTable
    participant Exporter as Export\nHandlers

    User->>Header: type searchString
    Header->>Table: update tableData (filter subjects)
    User->>LookupBtn: click
    LookupBtn->>Dialog: open IdentificationForm
    Dialog->>Table: select subject -> navigate to ./<id>/table
    User->>Exporter: request export
    Exporter->>Table: read tableData (listedSubjects)
    Exporter->>User: show info notification -> deliver file or error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Inspect filter logic and root$ stripping for edge cases.
  • Confirm all export handlers use the same filtered dataset and preserve field order.
  • Verify useEffect dependencies to avoid stale or extra renders.
  • Check Dialog.Trigger accessibility, focus behavior, and IdentificationForm navigation wiring.

Possibly related PRs

  • New features #1253 — modifies the same Datahub page and changes MasterDataTable subject navigation target to ./<id>/table.

Suggested reviewers

  • joshunrau

Pre-merge checks and finishing touches

✅ 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 'Improve datahub search' directly reflects the main changes: adding a new search bar to filter datahub table data and improving the search functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d5edbc and 18cbb36.

📒 Files selected for processing (3)
  • apps/web/src/providers/WalkthroughProvider.tsx (1 hunks)
  • apps/web/src/routes/_app/datahub/index.tsx (7 hunks)
  • apps/web/src/styles.css (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/routes/_app/datahub/index.tsx (4)
packages/schemas/src/subject/subject.ts (1)
  • Subject (19-19)
packages/subject-utils/src/index.ts (1)
  • removeSubjectIdScope (34-39)
packages/runtime-core/src/notifications.ts (1)
  • addNotification (14-16)
apps/web/src/utils/excel.ts (1)
  • downloadExcel (4-8)
⏰ 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 (8)
apps/web/src/providers/WalkthroughProvider.tsx (1)

143-156: The target selector is valid.

The element with ID datahub-subject-search-bar exists in apps/web/src/routes/_app/datahub/index.tsx (lines 189-190) and has corresponding CSS styling. The walkthrough step will correctly target this element.

apps/web/src/styles.css (1)

11-11: LGTM - Selector updated to match component refactoring.

The ID selector correctly reflects the renamed search bar component.

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

1-20: LGTM - Imports align with new functionality.

All new imports (useEffect, useState, Button, SearchBar, UserSearchIcon) are utilized in the component.


81-82: LGTM - State initialization is safe.

Using data ?? [] prevents undefined issues during initial render.


93-148: LGTM - Export logic correctly filters and validates data.

The implementation properly:

  • Uses removeSubjectIdScope for consistent ID normalization
  • Filters exports to match visible table data
  • Validates non-empty results before export
  • Provides clear user feedback via notifications

Enhanced error handling correctly differentiates between Error instances and unknown errors.


150-161: LGTM - Navigation updated per PR objectives.

Subject lookup now correctly routes to the table view instead of assignments.


187-221: LGTM - UI components properly structured.

The SearchBar and Dialog.Trigger Button have unique IDs (no conflicts), proper event handlers, and bilingual support. The restructuring aligns with the PR objectives.


233-238: LGTM - Table correctly uses filtered data.

Using tableData as the data source enables the search filtering functionality. Navigation to table view is consistent with the lookup flow.


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.

@david-roper david-roper marked this pull request as ready for review December 9, 2025 18:49
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: 2

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

104-104: Avoid explicit any type.

The : any return type annotation suppresses type checking. Remove it or use a proper type.

     getExportRecords()
-      .then((data): any => {
+      .then((data) => {
📜 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 8190f26.

📒 Files selected for processing (1)
  • apps/web/src/routes/_app/datahub/index.tsx (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/routes/_app/datahub/index.tsx (3)
packages/schemas/src/subject/subject.ts (1)
  • Subject (19-19)
vendor/zod@3.x/src/v3.d.ts (1)
  • record (2499-2499)
apps/web/src/utils/excel.ts (1)
  • downloadExcel (4-8)
🔇 Additional comments (4)
apps/web/src/routes/_app/datahub/index.tsx (4)

1-25: Imports look good.

All new imports (useEffect, Button, UserSearchIcon) are used in the component.


82-83: State initialization relies on useEffect sync.

The tableData initialization from data works because the useEffect (lines 150-164) re-syncs when data changes. This is acceptable but consider using a derived value pattern if React 19's features allow.


150-164: Filtering logic is sound.

The useEffect correctly syncs tableData with data and applies the search filter. Dependencies are complete.


219-223: Filtered data correctly passed to table.

Using tableData ensures the table reflects the search filter.

Note: Row selection navigates to ./table (line 222) while subject lookup navigates to ./assignments (line 146). Verify this is intentional.

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

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

188-199: Clean up Dialog trigger Button classes and IDs for clarity.

The trigger Button still carries [&>input]-scoped classes from the old SearchBar and uses “search-bar” in its id/testid, which no longer reflects its role as a lookup trigger. You can simplify the styles and make the identifiers clearer:

-          <Dialog open={isLookupOpen} onOpenChange={setIsLookupOpen}>
-            <Dialog.Trigger>
-              <Button
-                className="[&>input]:text-foreground [&>input]:placeholder-foreground grow gap-1"
-                data-testid="datahub-subject-lookup-search"
-                id="subject-lookup-search-bar"
-              >
+          <Dialog open={isLookupOpen} onOpenChange={setIsLookupOpen}>
+            <Dialog.Trigger>
+              <Button
+                className="grow gap-1"
+                data-testid="datahub-subject-lookup-button"
+                id="subject-lookup-button"
+              >
                 <UserSearchIcon />{' '}
                 {t({
                   en: 'Subject Lookup',
                   fr: 'Trouver un client'
                 })}
               </Button>
             </Dialog.Trigger>

This removes dead CSS selectors and avoids conflating the trigger with a search input.

🧹 Nitpick comments (2)
apps/web/src/routes/_app/datahub/index.tsx (2)

40-41: Reuse removeSubjectIdScope instead of duplicating regex logic.

You already import and use removeSubjectIdScope for display, but export/filter paths still hand-roll the same normalization via regex. Centralizing this reduces drift and keeps behavior consistent across UI, filtering, and exports.

-        const listedSubjects = tableData.map((record) => {
-          return record.id.replace(/^.*?\$/, '');
-        });
+        const listedSubjects = tableData.map((record) => removeSubjectIdScope(record.id));
-    const filtered = data.filter((record) =>
-      record.id
-        .replace(/^.*?\$/, '')
-        .toLowerCase()
-        .includes(searchString.toLowerCase())
-    );
+    const filtered = data.filter((record) =>
+      removeSubjectIdScope(record.id)
+        .toLowerCase()
+        .includes(searchString.toLowerCase())
+    );

Please double-check that removeSubjectIdScope’s behavior matches the regex assumption (strip everything up to the first $).

Also applies to: 105-107, 156-160


94-120: Avoid any in the export pipeline to keep InstrumentRecordsExport typed.

getExportRecords is already typed to return InstrumentRecordsExport, so explicitly annotating the .then parameter as any drops useful type checking for subjectId and friends. Let TypeScript infer the type (or make it explicit) instead:

-    getExportRecords()
-      .then((data): any => {
+    getExportRecords()
+      .then((data) => {
         const listedSubjects = tableData.map((record) => {
           return record.id.replace(/^.*?\$/, '');
         });

         const filteredData = data.filter((dataEntry) => listedSubjects.includes(dataEntry.subjectId));

         switch (option) {
           case 'CSV':
             void download('README.txt', t('datahub.index.table.exportHelpText'));
             void download(`${baseFilename}.csv`, unparse(filteredData));
             break;
           case 'Excel':
             return downloadExcel(`${baseFilename}.xlsx`, filteredData);
           case 'JSON':
             return download(`${baseFilename}.json`, JSON.stringify(filteredData, null, 2));
         }
       })

This keeps the nice “export only what’s currently listed” behavior while preserving static safety.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8190f26 and ded6a6f.

📒 Files selected for processing (1)
  • apps/web/src/routes/_app/datahub/index.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/routes/_app/datahub/index.tsx (2)
packages/schemas/src/subject/subject.ts (1)
  • Subject (19-19)
apps/web/src/utils/excel.ts (1)
  • downloadExcel (4-8)
⏰ 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 (2)
apps/web/src/routes/_app/datahub/index.tsx (2)

81-84: Ensure data is always an array before using it in state/effect.

Both useState<Subject[]>(data) and data.filter(...) assume data is a Subject[]. If useSubjectsQuery ever returns undefined during loading or error, MasterDataTable can receive undefined and the effect will throw on data.filter. Safer to normalize to an empty array once and reuse that in state and filtering:

-  const { data } = useSubjectsQuery({ params: { groupId: currentGroup?.id } });
-  const [tableData, setTableData] = useState<Subject[]>(data);
+  const { data } = useSubjectsQuery({ params: { groupId: currentGroup?.id } });
+  const subjects = data ?? [];
+  const [tableData, setTableData] = useState<Subject[]>(subjects);

   useEffect(() => {
     if (!searchString) {
-      setTableData(data);
+      setTableData(subjects);
       return;
     }

-    const filtered = data.filter((record) =>
+    const filtered = subjects.filter((record) =>
       record.id
         .replace(/^.*?\$/, '')
         .toLowerCase()
         .includes(searchString.toLowerCase())
     );

     setTableData(filtered);
-  }, [searchString, data]);
+  }, [searchString, data]);

Also applies to: lines 150-165


137-147: Verify navigation targets: lookup goes to /assignments, table rows to /table.

lookupSubject navigates to ./${response.data.id}/assignments, while row selection in MasterDataTable now goes to ./${subject.id}/table. If both flows are meant to land users on the same "subject view", this divergence may be confusing. If the intent is for both to open the new table view, update the lookup navigation accordingly; if the difference is intentional, consider documenting it or naming the actions to make the distinction clear.

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

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)

137-147: Align subject navigation between lookup and table selection.

onSelect now navigates to ./${subject.id}/table, but lookupSubject still sends users to ./${response.data.id}/assignments. For a consistent UX (and to match the walkthrough steps that point at /datahub/123/table), consider routing the lookup flow to the table view as well:

-      await navigate({ to: `./${response.data.id}/assignments` });
+      await navigate({ to: `./${response.data.id}/table` });

Unless there’s a deliberate reason to land on Assignments here, this change would make the two entry points behave consistently.

Also applies to: 224-225

🧹 Nitpick comments (3)
apps/web/src/providers/WalkthroughProvider.tsx (1)

140-176: Walkthrough targeting matches new UI; minor French copy fix.

The updated targets #datahub-subject-search-bar and [data-spotlight-type="subject-lookup-search-button"] correctly match the new Datahub search bar and lookup button. One small nit: in the French text for the Subject Lookup step, "cliquez sur la button de recherche" should be "cliquez sur le bouton de recherche" for correct grammar.

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

81-84: Harden subject filtering state and reuse ID normalization logic.

The new tableData/searchString flow and tying exports to tableData look good, but a few small tweaks would make this more robust and maintainable:

  • If useSubjectsQuery can ever yield undefined during loading, useState<Subject[]>(data) and setTableData(data) will temporarily push undefined into tableData, which could break .map/ClientTable. To be defensive, default data to an empty array and use that consistently in the effect:
-  const { data } = useSubjectsQuery({ params: { groupId: currentGroup?.id } });
-  const [tableData, setTableData] = useState<Subject[]>(data);
+  const { data = [] } = useSubjectsQuery({ params: { groupId: currentGroup?.id } });
+  const [tableData, setTableData] = useState<Subject[]>(data);
  • You’re duplicating the prefix-stripping regex in both the export path and the search filter. Since removeSubjectIdScope is already imported, using it here will keep ID normalization in one place:
-        const listedSubjects = tableData.map((record) => {
-          return record.id.replace(/^.*?\$/, '');
-        });
+        const listedSubjects = tableData.map((record) => removeSubjectIdScope(record.id));
-    const filtered = data.filter((record) =>
-      record.id
-        .replace(/^.*?\$/, '')
-        .toLowerCase()
-        .includes(searchString.toLowerCase())
-    );
+    const filtered = data.filter((record) =>
+      removeSubjectIdScope(record.id).toLowerCase().includes(searchString.toLowerCase())
+    );
  • For large datasets, listedSubjects.includes(dataEntry.subjectId) is O(n·m). Consider using a Set to make the export filter O(n + m):
-        const listedSubjects = tableData.map((record) => removeSubjectIdScope(record.id));
-
-        const filteredData = data.filter((dataEntry) => listedSubjects.includes(dataEntry.subjectId));
+        const listedSubjects = new Set(
+          tableData.map((record) => removeSubjectIdScope(record.id))
+        );
+
+        const filteredData = data.filter((dataEntry) =>
+          listedSubjects.has(dataEntry.subjectId)
+        );

Also applies to: 105-110, 150-164, 222-222


175-201: Clean up lookup button classes; SearchBar wiring looks good.

The header SearchBar correctly controls searchString and ties into the filtering effect, and the new lookup trigger Button is wired properly to the dialog and walkthrough attributes.

The Button’s className still contains [&>input]:… utilities that only applied when this was a SearchBar:

<Button
  className="[&>input]:text-foreground [&>input]:placeholder-foreground gap-1"
  
>

Since a Button has no child <input>, these selectors are now dead and can be dropped for clarity:

-              <Button
-                className="[&>input]:text-foreground [&>input]:placeholder-foreground gap-1"
+              <Button
+                className="gap-1"
                 data-spotlight-type="subject-lookup-search-button"
                 data-testid="subject-lookup-search-button"
                 id="subject-lookup-search-button"
                 variant="outline"
               >

Optionally, you might also update the search bar placeholder from “Click to Search” to something like “Type to search” now that it’s directly editable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ded6a6f and a5605e2.

📒 Files selected for processing (3)
  • apps/web/src/providers/WalkthroughProvider.tsx (1 hunks)
  • apps/web/src/routes/_app/datahub/index.tsx (6 hunks)
  • apps/web/src/styles.css (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/routes/_app/datahub/index.tsx (2)
packages/schemas/src/subject/subject.ts (1)
  • Subject (19-19)
apps/web/src/utils/excel.ts (1)
  • downloadExcel (4-8)
⏰ 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/styles.css (1)

11-13: Selector rename aligns with new search bar ID.

The spotlight selector now correctly targets #datahub-subject-search-bar, matching the updated component and walkthrough usage. No issues here.

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 (1)
apps/web/src/routes/_app/datahub/index.tsx (1)

188-201: Clean up input-specific Tailwind selectors on the Dialog trigger Button

The trigger Button still has [&>input]:… selectors, which were useful for the old SearchBar but are now dead weight on a plain button.

You can simplify the class for clarity:

-              <Button
-                className="[&>input]:text-foreground [&>input]:placeholder-foreground gap-1"
+              <Button
+                className="gap-1"
                 data-spotlight-type="subject-lookup-search-button"
                 data-testid="subject-lookup-search-button"
                 id="subject-lookup-search-button"
                 variant="outline"
               >

IDs and test IDs are now unique relative to the header search, which fixes the earlier duplication issue.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5605e2 and 2ce6d23.

📒 Files selected for processing (1)
  • apps/web/src/routes/_app/datahub/index.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/routes/_app/datahub/index.tsx (2)
packages/schemas/src/subject/subject.ts (1)
  • Subject (19-19)
apps/web/src/utils/excel.ts (1)
  • downloadExcel (4-8)
⏰ 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 (3)
apps/web/src/routes/_app/datahub/index.tsx (3)

1-1: Imports updated appropriately

The additional React hooks, UI components, and UserSearchIcon match the new stateful search UI and subject lookup button usage; no issues here.

Also applies to: 4-11, 18-18


146-147: Navigation path change to ./${id}/table looks consistent

Both the lookup flow and table row selection now navigate to ./${id}/table, which keeps subject routing consistent with the new design.

Also applies to: 223-225


175-186: Header subject SearchBar wiring looks good

Binding the header SearchBar to searchString with onValueChange drives the client-side filtering cleanly; data-testid/id are unique and match the new walkthrough/spotlight usage.

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

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

81-82: Guard against undefined data — unresolved from past review.

This issue was previously flagged but not addressed. When useSubjectsQuery is loading, data is undefined, causing tableData to be undefined and breaking the export handler's .map() call at line 97.

Apply this diff:

- const [tableData, setTableData] = useState<Subject[]>(data);
+ const [tableData, setTableData] = useState<Subject[]>(data ?? []);

163-174: Guard against undefined data in filter effect.

When data is undefined, line 169's data.filter() will throw. This is related to the initialization issue above.

Apply this diff:

  useEffect(() => {
+   const source = data ?? [];
+
    if (!searchString) {
-     setTableData(data);
+     setTableData(source);
      return;
    }

-   const filtered = data.filter((record) =>
+   const filtered = source.filter((record) =>
      removeSubjectIdScope(record.id).toLowerCase().includes(searchString.toLowerCase())
    );

    setTableData(filtered);
  }, [searchString, data]);
🧹 Nitpick comments (1)
apps/web/src/routes/_app/datahub/index.tsx (1)

200-200: Remove unused input-targeting styles from Button.

The [&>input]:text-foreground [&>input]:placeholder-foreground selectors target input children, but this Button has none. Leftover from the SearchBar styling.

Apply this diff:

              <Button
-               className="[&>input]:text-foreground [&>input]:placeholder-foreground gap-1"
+               className="gap-1"
                data-spotlight-type="subject-lookup-search-button"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ce6d23 and d66e9dd.

📒 Files selected for processing (1)
  • apps/web/src/routes/_app/datahub/index.tsx (7 hunks)
⏰ 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 (3)
apps/web/src/routes/_app/datahub/index.tsx (3)

97-107: Export filtering logic is correct.

The consistent use of removeSubjectIdScope and the non-empty validation check are good improvements from the past review.


159-159: Navigation target updated correctly.

The change to navigate to the table page aligns with the PR objectives.


232-234: Filtered table data and navigation are correct.

Using tableData for the filtered view and navigating to the table page both align with the PR objectives.

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

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

81-83: Guard search filtering against data being undefined

The new client‑side filtering is almost there, but there’s still a crash path if the user types into the search bar before useSubjectsQuery has resolved:

  • Line 164 safely derives definedTableData = data ?? [].
  • Line 171 then calls data.filter(...) directly. If data is still undefined and searchString is non‑empty, this becomes undefined.filter(...) and throws.

You can fix this by using a single local source array derived from data for both the “no search” and “filtered” branches:

-  useEffect(() => {
-    const definedTableData = data ?? [];
-
-    if (!searchString) {
-      setTableData(definedTableData);
-      return;
-    }
-
-    const filtered = data.filter((record) =>
-      removeSubjectIdScope(record.id).toLowerCase().includes(searchString.toLowerCase())
-    );
-
-    setTableData(filtered);
-  }, [searchString, data]);
+  useEffect(() => {
+    const source = data ?? [];
+
+    if (!searchString) {
+      setTableData(source);
+      return;
+    }
+
+    const filtered = source.filter((record) =>
+      removeSubjectIdScope(record.id).toLowerCase().includes(searchString.toLowerCase())
+    );
+
+    setTableData(filtered);
+  }, [searchString, data]);

This keeps tableData in sync with the query results while avoiding any undefined access.

Also applies to: 163-176

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

80-83: Export filtering and notifications look good; sanitize filenames for cross‑platform safety

Filtering export records by the currently listed subjects (via tableData and removeSubjectIdScope) and failing fast when there are no entries to export is solid, and the pre‑export info + specific error messaging will give users clear feedback.

One improvement: baseFilename uses new Date().toISOString(), which includes : characters that are invalid in filenames on some platforms and can cause odd browser sanitization. Consider normalizing the timestamp before composing the filename:

-  const handleExportSelection = (option: 'CSV' | 'Excel' | 'JSON') => {
-    const baseFilename = `${currentUser!.username}_${new Date().toISOString()}`;
+  const handleExportSelection = (option: 'CSV' | 'Excel' | 'JSON') => {
+    const safeTimestamp = new Date().toISOString().replace(/[:]/g, '-');
+    const baseFilename = `${currentUser!.username}_${safeTimestamp}`;

Also applies to: 93-107, 109-116, 120-125, 128-147

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d66e9dd and 7d5edbc.

📒 Files selected for processing (1)
  • apps/web/src/routes/_app/datahub/index.tsx (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/routes/_app/datahub/index.tsx (3)
packages/schemas/src/subject/subject.ts (1)
  • Subject (19-19)
packages/subject-utils/src/index.ts (1)
  • removeSubjectIdScope (34-39)
apps/web/src/utils/excel.ts (1)
  • downloadExcel (4-8)
⏰ 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 (2)
apps/web/src/routes/_app/datahub/index.tsx (2)

1-11: Imports match usage and keep concerns separated

React hooks, libui components (including SearchBar and Button), and UserSearchIcon are all used correctly and the imports are cleanly grouped. No issues here.

Also applies to: 18-18


150-160: Subject search, lookup, and navigation wiring align with PR goals

  • Header SearchBar correctly drives searchString, which feeds the filtered tableData and therefore the MasterDataTable.
  • The new subject lookup Dialog.Trigger button has unique id/data-testid and uses UserSearchIcon, avoiding the previous duplicate‑ID issue.
  • Both subject lookup success (lookupSubject) and row selection now navigate to ./${id}/table, matching the updated UX.

This all looks consistent and cohesive.

Also applies to: 187-198, 200-213, 234-237

@david-roper david-roper force-pushed the improve-datahub-search branch from 7d5edbc to 18cbb36 Compare December 11, 2025 15:22
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.

1 participant