Skip to content

Conversation

@amirrr
Copy link
Collaborator

@amirrr amirrr commented Dec 9, 2025

Summary by CodeRabbit

  • New Features
    • Dashboard adds a Recently Viewed Projects panel (up to 5 items) with project title/description, owner label (You/Collaborator/-), viewed timestamps, quick-open links, deleted-project styling, hover/disabled states, and a “+N more” indicator.
  • Backend / Data
    • Server records and returns recently viewed project entries and updates views asynchronously so the dashboard can display them.
  • Documentation
    • Minor docstring clarification in project models.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds per-user recently viewed project tracking (capped at 10) on the backend, exposes enriched recently_viewed data in project list/detail APIs, extends the User model, and surfaces up to 5 recently viewed items in the dashboard via a new frontend component.

Changes

Cohort / File(s) Summary
Frontend: Recently Viewed Component
client/src/components/View/DataGrid/RecentlyViewedProjects.tsx
New React TSX component rendering up to 5 recently viewed projects with columns Project/Owner/Viewed, handles missing/deleted projects, row navigation, external-link action, and a “+N more” indicator.
Frontend: Dashboard Integration
client/src/pages/Dashboard/Overview.tsx, client/src/pages/Dashboard/useOverviewData.tsx
Imported and rendered RecentlyViewedProjects in Overview; added RecentlyViewedProject type, recentlyViewed state, populated from API (response.data.recently_viewed) and returned from useOverviewData.
Backend: Tracking function
server/controllers/project.py
Added async track_project_view(user: User, project_id: str) that removes duplicate entries, prepends a timestamped entry, trims to 10 items, and saves the user; exceptions are caught and logged.
Backend: User model
server/database/models/users.py
Added recently_viewed_projects: List[dict] = Field(default_factory=list) to the User model.
Backend: Projects model docstring
server/database/models/projects.py
Updated module docstring from "Papers model." to "Projects model." (documentation-only change).
Backend: Routes & Response enrichment
server/routes/v1/projects.py
Imported track_project_view and User typing; enriched GET /projects with recently_viewed entries (including project data, owner_email, is_owner, ISO timestamps); schedules async track_project_view on GET /projects/; added User type hints to several handlers.

Sequence Diagram

sequenceDiagram
    actor User
    participant Browser as Client
    participant Server
    participant DB as Database

    User->>Browser: Click project (open detail)
    Browser->>Server: GET /projects/:id
    Server->>DB: Fetch project
    Server-->>Browser: Return project data

    par Async track
        Server->>Server: schedule track_project_view(user, project_id)
        Server->>DB: Read user.recently_viewed_projects
        Server->>DB: Remove existing entry for project_id
        Server->>DB: Prepend new entry with timestamp
        Server->>DB: Trim list to 10 and save user
    end

    User->>Browser: Load dashboard overview
    Browser->>Server: GET /projects
    Server->>DB: Fetch projects and user.recently_viewed_projects
    Server->>Server: Enrich recently_viewed with project details, is_owner, ISO timestamps
    Server-->>Browser: Return projects + recently_viewed
    Browser->>Browser: Render RecentlyViewedProjects (top 5, "+N more")
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review enrichment logic and edge cases in server/routes/v1/projects.py (missing projects, ownership flags, timestamp handling).
  • Verify concurrency/scheduling and error handling in server/controllers/project.py.
  • Confirm recently_viewed_projects schema in server/database/models/users.py matches API and frontend expectations.
  • Check types and prop alignment between useOverviewData and RecentlyViewedProjects component.

Poem

🐰 I hopped through code to mark each view,
Ten memories kept, then trimmed anew,
Five show on dashboard in a tidy row,
Click and I’ll carry you back where thoughts grow,
A little rabbit keeps your recent flow.

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 accurately describes the main change: adding a Recently Viewed Projects feature with both tracking (backend) and display (frontend) functionality, which aligns with all modified files across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.

📜 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 1d490a6 and 42996d6.

📒 Files selected for processing (4)
  • client/src/components/View/DataGrid/RecentlyViewedProjects.tsx (1 hunks)
  • client/src/pages/Dashboard/useOverviewData.tsx (4 hunks)
  • server/controllers/project.py (1 hunks)
  • server/routes/v1/projects.py (6 hunks)

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

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
client/src/pages/Dashboard/useOverviewData.tsx (1)

78-113: refetchProjects doesn't update recentlyViewed state.

The initial fetch updates both projects and recentlyViewed (lines 63, 66-67), but refetchProjects only updates projects. This inconsistency means the recently viewed list becomes stale after a refetch, potentially showing outdated information.

Apply this diff to maintain consistency:

   const refetchProjects = useCallback(async () => {
     setIsLoadingProjects(true)
     try {
       const response = await api.get(`/v1/projects`)
       if (response.status !== 200) {
         throw new Error('Network response was not ok')
       }
 
       setProjects(
         response.data.project.map(
           (project: {
             id: string
             title: string
             description: string
             papers: unknown[]
             results: { id: string; finished: boolean; paper_id: string }[]
           }) => {
             const uniquePaperIds = Array.from(
               new Set(project.results.map((result) => result.paper_id)),
             )
             return {
               id: project.id,
               name: project.title,
               description: project.description,
               paper_count: uniquePaperIds.length,
               results: project.results,
             } as Projects
           },
         ),
       )
+
+      if (response.data.recently_viewed) {
+        setRecentlyViewed(response.data.recently_viewed)
+      }
     } catch (error) {
       console.error(error)
     } finally {
       setIsLoadingProjects(false)
     }
   }, [])
🧹 Nitpick comments (5)
server/database/models/users.py (1)

22-24: Consider using a typed model instead of List[dict] for better type safety.

The recently_viewed_projects field uses an untyped List[dict], which loses compile-time type checking and IDE support. Consider defining a typed model:

+from pydantic import BaseModel
+
+class RecentlyViewedProject(BaseModel):
+    project_id: str
+    viewed_at: datetime
+
 class User(Document):
     ...
-    recently_viewed_projects: List[dict] = Field(default_factory=list)
+    recently_viewed_projects: List[RecentlyViewedProject] = Field(default_factory=list)

This would provide validation, better IDE autocompletion, and catch schema mismatches early.

server/controllers/project.py (1)

196-200: Replace print() with proper logging.

Using print() for logging is not production-ready. The codebase already imports logging in the routes file.

+import logging
+
+logger = logging.getLogger(__name__)
+
 async def track_project_view(user: User, project_id: str):
     ...
-        print(f"Tracked project view for user {user.email} and project {project_id}")
+        logger.info(f"Tracked project view for user {user.email} and project {project_id}")
 
     except Exception as e:
         # Log error but don't fail the request
-        print(f"Error tracking project view: {e}")
+        logger.warning(f"Error tracking project view: {e}", exc_info=True)

Note: The broad Exception catch (Ruff BLE001) is acceptable here since the intent is to never fail the request, but logger.warning with exc_info=True will preserve the stack trace for debugging.

client/src/pages/Dashboard/Overview.tsx (1)

43-44: Consider showing a loading state for recently viewed projects.

While RecentlyViewedProjects returns null for empty arrays, users might see a brief layout shift when data loads. If isLoadingProjects also applies to recently viewed data, consider conditionally rendering a skeleton.

This is a minor UX consideration and can be deferred if the loading is fast enough to not cause noticeable shifts.

server/routes/v1/projects.py (1)

125-132: Add defensive handling for viewed_at serialization.

The code assumes view["viewed_at"] is always a datetime object. If the data is malformed (e.g., already a string from older data or corruption), calling .isoformat() will fail.

+            viewed_at = view["viewed_at"]
+            if isinstance(viewed_at, datetime):
+                viewed_at_str = viewed_at.isoformat()
+            else:
+                viewed_at_str = str(viewed_at)
+
             recently_viewed_enriched.append(
                 {
                     "project_id": project_id,
-                    "viewed_at": view["viewed_at"].isoformat(),
+                    "viewed_at": viewed_at_str,
                     "project": project_data,
                     "exists": project_data is not None,
                 }
             )
client/src/components/View/DataGrid/RecentlyViewedProjects.tsx (1)

69-69: Unsafe assumption that email contains '@' symbol.

The split('@')[0] will not throw but returns the full string if '@' is missing. While unlikely, malformed emails from the backend (or test data) could produce unexpected UI output.

Apply this diff for safer extraction:

-                      view.project.owner_email.split('@')[0]
+                      view.project.owner_email.split('@')[0] || view.project.owner_email

Or use a helper function:

const getEmailUsername = (email: string) => {
  const parts = email.split('@')
  return parts[0] || email
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef79fd2 and eaa10f2.

📒 Files selected for processing (7)
  • client/src/components/View/DataGrid/RecentlyViewedProjects.tsx (1 hunks)
  • client/src/pages/Dashboard/Overview.tsx (2 hunks)
  • client/src/pages/Dashboard/useOverviewData.tsx (4 hunks)
  • server/controllers/project.py (1 hunks)
  • server/database/models/projects.py (1 hunks)
  • server/database/models/users.py (2 hunks)
  • server/routes/v1/projects.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
client/src/pages/Dashboard/Overview.tsx (1)
client/src/components/View/DataGrid/RecentlyViewedProjects.tsx (1)
  • RecentlyViewedProjects (18-103)
client/src/pages/Dashboard/useOverviewData.tsx (2)
server/routes/v1/projects.py (1)
  • project (37-136)
client/src/components/View/DataGrid/ProjectsTable.tsx (1)
  • Projects (39-49)
client/src/components/View/DataGrid/RecentlyViewedProjects.tsx (1)
client/src/pages/Dashboard/useOverviewData.tsx (1)
  • RecentlyViewedProject (7-17)
server/controllers/project.py (1)
server/database/models/users.py (1)
  • User (11-54)
🪛 Ruff (0.14.8)
server/controllers/project.py

198-198: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (6)
server/database/models/projects.py (1)

1-3: LGTM!

The docstring correction from "Papers model." to "Projects model." accurately reflects the file's purpose.

server/database/models/users.py (1)

40-54: Verify if recently_viewed_projects should be included in to_dict().

The to_dict() method does not include the new recently_viewed_projects field. If this field needs to be serialized elsewhere (e.g., for API responses or debugging), consider adding it. If the omission is intentional for privacy reasons, this is fine as-is.

client/src/pages/Dashboard/Overview.tsx (1)

6-10: LGTM!

Clean integration of the RecentlyViewedProjects component. The component handles empty state gracefully by returning null, making additional conditional rendering unnecessary.

server/routes/v1/projects.py (2)

156-159: Good use of non-blocking task for view tracking.

Using request.app.add_task() to schedule the tracking asynchronously is the right approach—it prevents the view tracking from delaying the response to the user.


109-118: Verify if exposing owner_email is intended.

The owner_email field is included for all recently viewed projects, which exposes other users' email addresses to the requesting user. Ensure this aligns with privacy requirements. If emails should be partially obscured, consider masking (e.g., j***@example.com) or using display names instead.

client/src/components/View/DataGrid/RecentlyViewedProjects.tsx (1)

47-51: Navigation pattern uses window.open instead of React Router.

Both the row click and the external link button use window.open to open projects in a new tab. This bypasses React Router and can create a jarring user experience. Verify this is intentional for cross-context navigation.

If in-app navigation is preferred, consider using React Router's navigate:

import { useNavigate } from 'react-router-dom'

// Inside component:
const navigate = useNavigate()

// For row click:
onClick={() => {
  if (view.exists) {
    navigate(`/project/${view.project_id}`)
  }
}}

// For external link button, you can still use target="_blank" with Link:
<Link to={`/project/${view.project_id}`} target="_blank">
  <Button variant='ghost' size='sm' className='h-6 w-6 p-0'>
    <ExternalLink className='h-3.5 w-3.5' />
  </Button>
</Link>

If opening in new tabs is intentional (e.g., for comparing projects), consider adding rel="noopener noreferrer" for security.

Also applies to: 84-87

Copy link

@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)
server/routes/v1/projects.py (1)

96-111: Harden recently_viewed_projects against malformed project_id values.

Directly doing PydanticObjectId(view["project_id"]) will raise if any stored project_id is malformed (data corruption, manual edits, legacy data), causing the entire /projects GET to fail instead of just skipping bad entries. Please catch and skip/log invalid ids so the endpoint remains robust. This was already flagged in a previous review and still applies.

Suggested adjustment:

-        recently_viewed_project_ids = [
-            PydanticObjectId(view["project_id"])
-            for view in user.recently_viewed_projects
-        ]
+        recently_viewed_project_ids: list[PydanticObjectId] = []
+        for view in user.recently_viewed_projects:
+            project_id = view.get("project_id")
+            if not project_id:
+                continue
+            try:
+                recently_viewed_project_ids.append(PydanticObjectId(project_id))
+            except Exception:
+                logger.warning(
+                    "Skipping invalid project_id in recently_viewed for user %s: %r",
+                    user.id,
+                    project_id,
+                )

If you prefer to hide such entries entirely, you can also continue later when project_data is None before appending to recently_viewed_enriched.

🧹 Nitpick comments (2)
server/routes/v1/projects.py (2)

10-16: Non-blocking track_project_view usage is good; consider guarding background failures.

Using request.app.add_task(track_project_view(user, project_id)) keeps the GET fast, which is appropriate. To avoid silent loss of tracking if track_project_view throws (DB/network issues, etc.), consider wrapping its body in a small wrapper coroutine with try/except + logging, or handling errors inside track_project_view itself so background exceptions are always captured and logged explicitly.

Also applies to: 160-162


123-135: Make view["viewed_at"].isoformat() resilient to legacy/string data.

This assumes view["viewed_at"] is always a datetime. If any older recently_viewed_projects entries stored viewed_at as a string (e.g., ISO string) or None, this will raise and break the endpoint. If there’s any chance of mixed data, consider normalizing first, e.g.:

viewed_at = view.get("viewed_at")
if hasattr(viewed_at, "isoformat"):
    viewed_at_str = viewed_at.isoformat()
elif isinstance(viewed_at, str):
    viewed_at_str = viewed_at  # or parse/normalize if needed
else:
    viewed_at_str = None

and then use viewed_at_str in the response.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eaa10f2 and 1d490a6.

📒 Files selected for processing (1)
  • server/routes/v1/projects.py (6 hunks)
🔇 Additional comments (1)
server/routes/v1/projects.py (1)

42-44: Consistent User typing for request.ctx.user looks solid.

Using user: User = request.ctx.user in all handlers improves type checking and readability, and the downstream uses (user.id, user.email) are consistent with that type. No issues here.

Also applies to: 152-153, 199-200, 314-315

@amirrr amirrr merged commit ac306e8 into main Dec 9, 2025
3 of 5 checks passed
@amirrr amirrr deleted the add-recently-viewed-table branch December 9, 2025 17:16
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