Skip to content

Admin data room search#2115

Open
mfts wants to merge 1 commit intomainfrom
cursor/admin-data-room-search-b5a0
Open

Admin data room search#2115
mfts wants to merge 1 commit intomainfrom
cursor/admin-data-room-search-b5a0

Conversation

@mfts
Copy link
Owner

@mfts mfts commented Mar 16, 2026

Adds a search box to the admin data room to enable searching for both documents and folders across the entire dataroom.

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Added search functionality to datarooms, allowing users to search documents and folders with results organized by type
    • Search results now display in grouped sections with folder and document counts, including navigable breadcrumb paths
    • Added a persistent search box integrated into the dataroom documents interface for convenient access

- API: new /api/teams/[teamId]/datarooms/[id]/search endpoint that
  searches both documents (by document name) and folders (by folder
  name) within a dataroom, returning path breadcrumbs for each result
- SWR: add useDataroomSearch hook with types for search results
- UI: add SearchBoxPersisted to both root and subfolder document pages;
  when a search query is active, display DataroomSearchResults instead
  of the normal folder/document list with reorder/DnD
- Search results show folders and documents separately with counts,
  each with clickable breadcrumb paths showing their location in the
  dataroom hierarchy
- Empty state shown when no results match the query

Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
@cursor
Copy link

cursor bot commented Mar 16, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@vercel
Copy link

vercel bot commented Mar 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
papermark Ready Ready Preview, Comment Mar 16, 2026 0:20am

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

Walkthrough

This PR introduces dataroom search functionality by adding a new API search endpoint, custom hook for fetching search results with folder path enrichment, a search results display component with breadcrumb navigation, and integrating search UI into existing dataroom document pages.

Changes

Cohort / File(s) Summary
API Search Endpoint
pages/api/teams/[teamId]/datarooms/[id]/search.ts
New API route implementing dataroom search with authentication, query validation, parallel document/folder filtering, and breadcrumb path construction for results.
Search Hook and Types
lib/swr/use-dataroom.ts
Added useDataroomSearch hook for client-side search fetching and two new types (DataroomFolderWithCountAndPath, DataroomFolderDocumentWithPath) extending existing types with folderPath array.
Search Results Component
components/datarooms/dataroom-search-results.tsx
New component rendering grouped search results by folders and documents with header counts, breadcrumb navigation via FolderPathBreadcrumb subcomponent, and dynamic folder linking.
Dataroom Document Pages
pages/datarooms/[id]/documents/[...name].tsx, pages/datarooms/[id]/documents/index.tsx
Integrated search functionality with SearchBoxPersisted component, useDataroomSearch hook, and conditional rendering between search results view and standard document list based on active search query.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "Admin data room search" directly and clearly describes the main feature added: search functionality for the admin data room interface.

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

📝 Coding Plan
  • Generate coding plan for human review comments

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: 4

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

Inline comments:
In `@pages/api/teams/`[teamId]/datarooms/[id]/search.ts:
- Around line 38-50: You're currently authorizing by team membership using
prisma.team.findUnique but not verifying the dataroom belongs to that team;
before performing the two searches (those that use dataroomId), query the
dataroom record with the provided dataroomId and teamId (e.g., via
prisma.dataroom.findFirst/findUnique where id = dataroomId and teamId = teamId)
and return 401/403 if not found, then proceed with the existing search logic so
only datarooms tied to the authorized team can be enumerated.
- Around line 124-160: The breadcrumb map is only populated when documents have
folderId, so folder results miss paths; change the folderId collection logic to
include folder matches as well (include IDs from documents and from the returned
folders array) and load allFoldersInDataroom whenever either set is non-empty
(use the combined folderIds length > 0). Also update how you compute folder
breadcrumbs for folder results: call buildBreadcrumb(folder.id) (not
folder.parentId) so the folder's own ancestry is included; references:
folderIds, allFoldersInDataroom, folderMap, buildBreadcrumb, documents,
foldersWithPath.

In `@pages/datarooms/`[id]/documents/[...name].tsx:
- Around line 41-45: The search branch is treating API errors and an unresolved
dataroom id as "no matches"; update the render logic that uses useDataroomSearch
(searchDocuments, searchFolders, isSearching, error) and DataroomSearchResults
so it first checks for search error (error) and that dataroom?.id is
defined/resolved before rendering results or lists; if error is present show the
error state (or error UI) and if dataroom?.id is undefined defer rendering (or
show loading), and pass a non-null dataroom id to DataroomSearchResults only
after resolution—apply the same guard to the other block referenced (lines
139-175) that forwards dataroom?.id!.

In `@pages/datarooms/`[id]/documents/index.tsx:
- Around line 40-44: The search branch currently ignores useDataroomSearch()'s
error and passes dataroom?.id! into DataroomSearchResults, causing backend
failures and unresolved dataroom ids to appear as empty results; update the
rendering logic that uses useDataroomSearch (references: searchDocuments,
searchFolders, isSearching, error) and DataroomSearchResults to: first check
error and render an error state (or early return) when error is present, and
ensure dataroom.id is a concrete value (use useDataroom() result rather than
dataroom?.id!) before passing it to child components; apply the same
guard/early-return pattern to the other search rendering block that mirrors this
logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bef6c6f9-98d3-4e9c-b666-b990c1b2351e

📥 Commits

Reviewing files that changed from the base of the PR and between d53a601 and 3c53355.

📒 Files selected for processing (5)
  • components/datarooms/dataroom-search-results.tsx
  • lib/swr/use-dataroom.ts
  • pages/api/teams/[teamId]/datarooms/[id]/search.ts
  • pages/datarooms/[id]/documents/[...name].tsx
  • pages/datarooms/[id]/documents/index.tsx

Comment on lines +38 to +50
try {
const team = await prisma.team.findUnique({
where: {
id: teamId,
users: {
some: { userId },
},
},
});

if (!team) {
return res.status(401).end("Unauthorized");
}
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 | 🔴 Critical

Bind the authorization check to the dataroom.

This route proves the caller belongs to teamId, but the two searches below are scoped only by dataroomId. A user who belongs to any team can pair that team's id with another team's dataroom id and enumerate foreign document/folder names. Please verify that dataroomId belongs to the authorized team before running the search.

Also applies to: 52-122

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

In `@pages/api/teams/`[teamId]/datarooms/[id]/search.ts around lines 38 - 50,
You're currently authorizing by team membership using prisma.team.findUnique but
not verifying the dataroom belongs to that team; before performing the two
searches (those that use dataroomId), query the dataroom record with the
provided dataroomId and teamId (e.g., via prisma.dataroom.findFirst/findUnique
where id = dataroomId and teamId = teamId) and return 401/403 if not found, then
proceed with the existing search logic so only datarooms tied to the authorized
team can be enumerated.

Comment on lines +124 to +160
// Build breadcrumb paths for documents that are in folders
const folderIds = [
...new Set(documents.filter((d) => d.folderId).map((d) => d.folderId!)),
];

const allFoldersInDataroom =
folderIds.length > 0
? await prisma.dataroomFolder.findMany({
where: { dataroomId },
select: { id: true, name: true, path: true, parentId: true },
})
: [];

const folderMap = new Map(
allFoldersInDataroom.map((f) => [f.id, f]),
);

const buildBreadcrumb = (folderId: string): string[] => {
const names: string[] = [];
let currentId: string | null = folderId;
while (currentId) {
const folder = folderMap.get(currentId);
if (!folder) break;
names.unshift(folder.name);
currentId = folder.parentId;
}
return names;
};

const documentsWithPath = documents.map((doc) => ({
...doc,
folderPath: doc.folderId ? buildBreadcrumb(doc.folderId) : [],
}));

const foldersWithPath = folders.map((folder) => ({
...folder,
folderPath: folder.parentId ? buildBreadcrumb(folder.parentId) : [],
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 | 🟠 Major

Include folder matches when building the breadcrumb map.

allFoldersInDataroom is only loaded when a matched document has a folderId, but foldersWithPath also calls buildBreadcrumb(). If a search returns nested folders and no documents, every folder result gets folderPath: [], so the breadcrumb UI disappears.

Suggested fix
-    const folderIds = [
-      ...new Set(documents.filter((d) => d.folderId).map((d) => d.folderId!)),
-    ];
+    const folderIds = new Set<string>();
+    for (const doc of documents) {
+      if (doc.folderId) folderIds.add(doc.folderId);
+    }
+    for (const folder of folders) {
+      if (folder.parentId) folderIds.add(folder.parentId);
+    }

     const allFoldersInDataroom =
-      folderIds.length > 0
+      folderIds.size > 0
         ? await prisma.dataroomFolder.findMany({
             where: { dataroomId },
             select: { id: true, name: true, path: true, parentId: true },
           })
         : [];
📝 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
// Build breadcrumb paths for documents that are in folders
const folderIds = [
...new Set(documents.filter((d) => d.folderId).map((d) => d.folderId!)),
];
const allFoldersInDataroom =
folderIds.length > 0
? await prisma.dataroomFolder.findMany({
where: { dataroomId },
select: { id: true, name: true, path: true, parentId: true },
})
: [];
const folderMap = new Map(
allFoldersInDataroom.map((f) => [f.id, f]),
);
const buildBreadcrumb = (folderId: string): string[] => {
const names: string[] = [];
let currentId: string | null = folderId;
while (currentId) {
const folder = folderMap.get(currentId);
if (!folder) break;
names.unshift(folder.name);
currentId = folder.parentId;
}
return names;
};
const documentsWithPath = documents.map((doc) => ({
...doc,
folderPath: doc.folderId ? buildBreadcrumb(doc.folderId) : [],
}));
const foldersWithPath = folders.map((folder) => ({
...folder,
folderPath: folder.parentId ? buildBreadcrumb(folder.parentId) : [],
// Build breadcrumb paths for documents that are in folders
const folderIds = new Set<string>();
for (const doc of documents) {
if (doc.folderId) folderIds.add(doc.folderId);
}
for (const folder of folders) {
if (folder.parentId) folderIds.add(folder.parentId);
}
const allFoldersInDataroom =
folderIds.size > 0
? await prisma.dataroomFolder.findMany({
where: { dataroomId },
select: { id: true, name: true, path: true, parentId: true },
})
: [];
const folderMap = new Map(
allFoldersInDataroom.map((f) => [f.id, f]),
);
const buildBreadcrumb = (folderId: string): string[] => {
const names: string[] = [];
let currentId: string | null = folderId;
while (currentId) {
const folder = folderMap.get(currentId);
if (!folder) break;
names.unshift(folder.name);
currentId = folder.parentId;
}
return names;
};
const documentsWithPath = documents.map((doc) => ({
...doc,
folderPath: doc.folderId ? buildBreadcrumb(doc.folderId) : [],
}));
const foldersWithPath = folders.map((folder) => ({
...folder,
folderPath: folder.parentId ? buildBreadcrumb(folder.parentId) : [],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/api/teams/`[teamId]/datarooms/[id]/search.ts around lines 124 - 160,
The breadcrumb map is only populated when documents have folderId, so folder
results miss paths; change the folderId collection logic to include folder
matches as well (include IDs from documents and from the returned folders array)
and load allFoldersInDataroom whenever either set is non-empty (use the combined
folderIds length > 0). Also update how you compute folder breadcrumbs for folder
results: call buildBreadcrumb(folder.id) (not folder.parentId) so the folder's
own ancestry is included; references: folderIds, allFoldersInDataroom,
folderMap, buildBreadcrumb, documents, foldersWithPath.

Comment on lines +41 to +45
const {
documents: searchDocuments,
folders: searchFolders,
isLoading: isSearching,
} = useDataroomSearch({ query: searchQuery });
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 | 🟠 Major

Don’t treat search errors as “no matches” here either.

This page has the same new failure mode as the root documents page: useDataroomSearch().error is ignored, so API failures fall through to DataroomSearchResults with empty arrays and render the empty state. The same switch also forwards dataroom?.id!, which can still be undefined on a direct ?search= load. Please gate the branch on error and a resolved dataroom id before rendering results or list content.

Also applies to: 139-175

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

In `@pages/datarooms/`[id]/documents/[...name].tsx around lines 41 - 45, The
search branch is treating API errors and an unresolved dataroom id as "no
matches"; update the render logic that uses useDataroomSearch (searchDocuments,
searchFolders, isSearching, error) and DataroomSearchResults so it first checks
for search error (error) and that dataroom?.id is defined/resolved before
rendering results or lists; if error is present show the error state (or error
UI) and if dataroom?.id is undefined defer rendering (or show loading), and pass
a non-null dataroom id to DataroomSearchResults only after resolution—apply the
same guard to the other block referenced (lines 139-175) that forwards
dataroom?.id!.

Comment on lines +40 to +44
const {
documents: searchDocuments,
folders: searchFolders,
isLoading: isSearching,
} = useDataroomSearch({ query: searchQuery });
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 | 🟠 Major

Don’t collapse search failures into the empty state.

useDataroomSearch() exposes error, but this branch ignores it and falls through to DataroomSearchResults with empty arrays, which will show “No documents or folders found” for backend failures. This block also still forwards dataroom?.id!, so a direct ?search= load can hand an invalid id to the child components before useDataroom() resolves. Please guard on error and a concrete dataroom id before rendering either branch.

Also applies to: 132-166

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

In `@pages/datarooms/`[id]/documents/index.tsx around lines 40 - 44, The search
branch currently ignores useDataroomSearch()'s error and passes dataroom?.id!
into DataroomSearchResults, causing backend failures and unresolved dataroom ids
to appear as empty results; update the rendering logic that uses
useDataroomSearch (references: searchDocuments, searchFolders, isSearching,
error) and DataroomSearchResults to: first check error and render an error state
(or early return) when error is present, and ensure dataroom.id is a concrete
value (use useDataroom() result rather than dataroom?.id!) before passing it to
child components; apply the same guard/early-return pattern to the other search
rendering block that mirrors this logic.

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