Skip to content

Conversation

@steveoni
Copy link
Member

@steveoni steveoni commented Nov 3, 2025

Summary by CodeRabbit

  • New Features

    • Pagination controls added to dataset lists for organizations and groups, enabling efficient navigation through dataset collections.
  • Improvements

    • Dataset lists now load dynamically with pagination support instead of displaying all results at once.
    • Updated package count display methodology for organizations and groups.

@vercel
Copy link

vercel bot commented Nov 3, 2025

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

Project Deployment Preview Comments Updated (UTC)
portaljs-cloud-frontend-template Ready Ready Preview Comment Nov 3, 2025 5:53pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Walkthrough

This PR refactors dataset list handling from static, pre-fetched props to dynamic server-fetched lists with client-side pagination. Key changes include converting DatasetList to accept organization/group metadata instead of datasets directly, introducing a new Pagination component for paginated display, updating page-level server-side logic to fetch initial datasets via searchDatasets, and replacing array-based package counts with direct count properties.

Changes

Cohort / File(s) Summary
Shared Pagination Component
components/_shared/PaginationOrgGroup.tsx
New Pagination component with prev/next navigation and page buttons; manages page grouping (10 pages per subset) and triggers onPageChange callback on selection.
DatasetList Refactoring
components/_shared/DatasetList.tsx
Converted from static prop-based rendering to dynamic server-fetched list with SWR; props changed from datasets array to type, name, and optional initialDatasets; added offset-based pagination and loading/empty states.
Info Components
components/groups/individualPage/GroupInfo.tsx, components/organization/individualPage/OrgInfo.tsx
Replaced packages?.length array-based counts with `package_count
Organization Page
pages/[org]/index.tsx
Modified server-side logic to fetch initialDatasets via searchDatasets instead of per-package dataset hydration; flipped include_datasets flag to false; updated OrgPage signature to accept initialDatasets and refactored DatasetList usage to type-based queries.
Group Page
pages/groups/[groupName].tsx
Similar to org page: replaced inline dataset fetching with searchDatasets call; flipped include_datasets to false; updated GroupPage signature and DatasetList invocation to use type="group" with initialDatasets.

Sequence Diagram

sequenceDiagram
    autonumber
    participant Browser
    participant Page (SSR)
    participant searchDatasets API
    participant DatasetList Component
    participant SWR Cache
    participant Pagination UI

    Note over Browser,Pagination UI: Server-Side Initial Fetch
    Browser->>Page (SSR): Request page
    Page (SSR)->>searchDatasets API: Fetch initialDatasets (offset=0, limit=10)
    searchDatasets API-->>Page (SSR): Return paginated datasets + count
    Page (SSR)-->>Browser: Render page with initialDatasets

    Note over Browser,Pagination UI: Client-Side Interaction
    Browser->>DatasetList Component: Mount with initialDatasets
    DatasetList Component->>SWR Cache: Use initialDatasets as fallbackData
    SWR Cache-->>DatasetList Component: Hydrate cache with initial data
    DatasetList Component->>Pagination UI: Render with count > limit
    
    Browser->>Pagination UI: Click next page
    Pagination UI->>DatasetList Component: Trigger onPageChange(offset=10)
    DatasetList Component->>SWR Cache: Fetch datasets at new offset via SWR
    SWR Cache->>searchDatasets API: Request datasets (offset=10, limit=10)
    searchDatasets API-->>SWR Cache: Return new dataset page
    SWR Cache-->>DatasetList Component: Update dataset list
    DatasetList Component->>Browser: Re-render with new page
    Browser->>Browser: Scroll to top
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • DatasetList signature and prop changes: Verify backward compatibility impact and confirm all call sites updated correctly (pages and any other consumers).
  • SWR integration in DatasetList: Review error handling, loading states, and proper cache invalidation/updates on pagination.
  • Pagination component logic: Validate offset/limit calculations, page grouping (max 10 per subset), and boundary conditions for prev/next button visibility.
  • Server-side searchDatasets calls: Ensure fq query parameter construction for type/name is correct and consistent across both pages.
  • Type safety: Confirm initialDatasets typing across DatasetList, OrgPage, and GroupPage doesn't introduce type mismatches.

Possibly related PRs

Suggested reviewers

  • demenech
  • willy1989cv
  • luccasmmg

Poem

🐰 Pages once heavy with datasets pre-cached,
Now fetch on demand—pagination attached!
The DatasetList learns to ask, not presume,
With SWR's magic and Pagination's room.
Swipe-hop, page-flip, the datasets bloom! 📄✨

Pre-merge checks and finishing touches

✅ 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 accurately summarizes the main changes: refactoring the DatasetList component to use dynamic server-fetched data with pagination for both groups and organizations.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch datasetlist

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

🧹 Nitpick comments (9)
pages/[org]/index.tsx (1)

67-67: Add TypeScript types for function parameters.

The function parameters lack type annotations. Adding explicit types improves type safety and IDE support.

Apply this diff:

-export default function OrgPage({ org, initialDatasets }): JSX.Element {
+export default function OrgPage({ 
+  org, 
+  initialDatasets 
+}: { 
+  org: Organization & { activity_stream?: any }; 
+  initialDatasets: any; 
+}): JSX.Element {

Note: Consider defining a more specific type for initialDatasets instead of any (e.g., the return type of searchDatasets).

pages/groups/[groupName].tsx (1)

59-59: Add TypeScript types for function parameters.

The function parameters lack type annotations. Adding explicit types improves type safety and IDE support.

Apply this diff:

-export default function GroupPage({ group, initialDatasets }): JSX.Element {
+export default function GroupPage({ 
+  group, 
+  initialDatasets 
+}: { 
+  group: Group & { activity_stream?: any }; 
+  initialDatasets: any; 
+}): JSX.Element {

Note: Consider defining a more specific type for initialDatasets instead of any (e.g., the return type of searchDatasets).

components/_shared/PaginationOrgGroup.tsx (4)

3-9: Consider exporting PaginationProps interface.

The PaginationProps interface is not exported, which may limit reusability and type checking in other parts of the codebase.

Apply this diff if the interface needs to be reused elsewhere:

-interface PaginationProps {
+export interface PaginationProps {

18-19: Hardcoded page size should match DatasetList limit.

The max = 10 is hardcoded here and must match the limit = 10 in DatasetList.tsx (line 18). Consider extracting this as a shared constant to prevent synchronization issues.

Create a shared constant file (e.g., lib/constants.ts):

export const DATASET_PAGE_SIZE = 10;

Then import and use it in both files:

+import { DATASET_PAGE_SIZE } from "@/lib/constants";
+
-  const max = 10;
+  const max = DATASET_PAGE_SIZE;

28-91: Add accessibility attributes to pagination controls.

The pagination buttons lack proper accessibility attributes, which may hinder screen reader users and keyboard navigation.

Consider adding:

  • aria-label attributes to Prev/Next buttons describing their action
  • aria-current="page" to the active page button
  • disabled attribute and styling when buttons shouldn't be clickable
  • Role attributes if needed

Example for the active page button:

   <button
     key={x}
     className={`${
       x === currentPage
         ? "bg-accent-100 !h-9 !w-9 rounded-[10px] text-black"
         : ""
     } px-2 font-semibold`}
     onClick={() => handlePageClick(x)}
+    aria-label={`Go to page ${x + 1}`}
+    aria-current={x === currentPage ? "page" : undefined}
     style={{
       display:
         x >= subsetOfPages && x < subsetOfPages + max ? "block" : "none",
     }}
   >
     {x + 1}
   </button>

68-68: Simplify the Next button visibility condition.

The condition count > max * max && (subsetOfPages + max) * max < count is complex and may be hard to maintain. Consider adding a comment or extracting it into a well-named variable.

Apply this diff:

-      {count > max * max && (subsetOfPages + max) * max < count && (
+      {(() => {
+        const hasMoreThan100Items = count > max * max;
+        const moreSubsetsExist = (subsetOfPages + max) * max < count;
+        return hasMoreThan100Items && moreSubsetsExist;
+      })() && (

Or add a comment:

+      {/* Show Next button if: more than 100 items total AND current subset window hasn't reached the end */}
       {count > max * max && (subsetOfPages + max) * max < count && (
components/_shared/DatasetList.tsx (3)

9-13: Replace any type for initialDatasets with a specific type.

The initialDatasets prop is typed as any, which loses type safety. It should match the return type of searchDatasets.

Define a proper type (you may need to adjust based on the actual return type):

+import { PackageSearchResult } from "@/lib/queries/dataset"; // or wherever the type is defined
+
 interface DatasetListProps {
   type: "organization" | "group";
   name: string;
-  initialDatasets?: any;
+  initialDatasets?: PackageSearchResult | null;
 }

15-18: Extract hardcoded limit as a shared constant.

The limit = 10 is hardcoded here and in PaginationOrgGroup.tsx (as max = 10). Extract this as a shared constant to prevent synchronization issues.

Create a shared constant file (e.g., lib/constants.ts):

export const DATASET_PAGE_SIZE = 10;

Then import and use it:

+import { DATASET_PAGE_SIZE } from "@/lib/constants";
+
-  const limit = 10;
+  const limit = DATASET_PAGE_SIZE;

24-44: Add error handling for the SWR fetcher function.

The fetcher function doesn't handle errors from searchDatasets. While SWR has built-in error handling, you should consider catching and logging errors for better debugging.

Apply this diff:

   const { data: searchResults, isValidating } = useSWR(
     ["entity_package_search", { fq, offset, limit }],
     async (api, options) => {
-      return searchDatasets({
-        fq: options.fq,
-        offset: options.offset,
-        limit: options.limit,
-        type: "dataset",
-        query: "",
-        sort: "metadata_modified desc",
-        groups: [],
-        orgs: [],
-        tags: [],
-        resFormat: [],
-      });
+      try {
+        return await searchDatasets({
+          fq: options.fq,
+          offset: options.offset,
+          limit: options.limit,
+          type: "dataset",
+          query: "",
+          sort: "metadata_modified desc",
+          groups: [],
+          orgs: [],
+          tags: [],
+          resFormat: [],
+        });
+      } catch (error) {
+        console.error("Failed to fetch datasets:", error);
+        throw error; // Re-throw to let SWR handle it
+      }
     },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2932a16 and ff0e674.

📒 Files selected for processing (6)
  • components/_shared/DatasetList.tsx (1 hunks)
  • components/_shared/PaginationOrgGroup.tsx (1 hunks)
  • components/groups/individualPage/GroupInfo.tsx (1 hunks)
  • components/organization/individualPage/OrgInfo.tsx (1 hunks)
  • pages/[org]/index.tsx (3 hunks)
  • pages/groups/[groupName].tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
components/_shared/DatasetList.tsx (2)
lib/queries/dataset.ts (1)
  • searchDatasets (13-97)
components/_shared/PaginationOrgGroup.tsx (1)
  • Pagination (11-92)
pages/groups/[groupName].tsx (2)
lib/queries/dataset.ts (1)
  • searchDatasets (13-97)
components/_shared/DatasetList.tsx (1)
  • DatasetList (15-96)
pages/[org]/index.tsx (2)
lib/queries/dataset.ts (1)
  • searchDatasets (13-97)
components/_shared/DatasetList.tsx (1)
  • DatasetList (15-96)
🔇 Additional comments (13)
components/organization/individualPage/OrgInfo.tsx (1)

44-44: LGTM! Package count source updated correctly.

The change from org.packages?.length to org.package_count || 0 correctly aligns with the new data structure where package counts are provided directly rather than via array length.

pages/[org]/index.tsx (4)

13-13: Import added correctly.

The searchDatasets import is properly added to support the new server-side initial dataset fetching.


30-30: Correct optimization: datasets fetched separately.

Setting include_datasets: false is appropriate since datasets are now fetched separately via searchDatasets for better performance and pagination support.


71-73: DatasetList integration looks good.

The updated DatasetList usage correctly passes type="organization", the organization name, and initialDatasets for server-side rendering with client-side pagination.


39-39: <!-- [scratchpad_start]
[task verification_1/1]
[observations]

  • searchDatasets function receives fq as-is from options and passes it directly to CKAN API (line 42-44, 66)
  • No normalization or sanitization of fq parameter occurs
  • Inconsistency confirmed across codebase:
    • pages/[org]/index.tsx:39 → organization:${org._name} (NO parentheses)
    • pages/groups/[groupName].tsx:32 → groups:${group._name} (NO parentheses)
    • components/_shared/DatasetList.tsx:21 → (organization:${name}) (WITH parentheses)
    • lib/queries/dataset.ts:47 → organization:(${joinTermsWithOr(...)}) (WITH parentheses, multi-value)
  • Patterns: single-value server-side = no parens; client-side and multi-value = with parens

[analysis]

  • Inconsistency is REAL and verifiable
  • fq parameters are combined with space joins which is AND logic in Solr/Lucene
  • Parentheses generally valid in both forms but affect query parsing/precedence
  • No code normalizes these values before CKAN API call
  • Potential risk: query behavior could differ based on parenthesization if CKAN is strict

[pending]

  • CKAN API documentation or behavior verification not possible without external testing
  • Both formats appear to work in Solr/Lucene but consistency is better practice

[actions]

  • Identify specific inconsistency locations
  • Determine if inconsistency needs fixing or if both are valid
  • Frame recommendation clearly

[done]

  • Verified inconsistency is real across four locations
  • Verified no normalization happens in searchDatasets function
  • Both parenthesized and non-parenthesized patterns exist in codebase
    </scratchpad_start] -->

Fix fq filter syntax inconsistency: use parentheses consistently.

An inconsistency exists in how the fq filter is formatted across the codebase:

  • pages/[org]/index.tsx:39 uses organization:${org._name} (no parentheses)
  • pages/groups/[groupName].tsx:32 uses groups:${group._name} (no parentheses)
  • components/_shared/DatasetList.tsx:21 uses (organization:${name}) (with parentheses)
  • lib/queries/dataset.ts:47 uses organization:(${joinTermsWithOr(...)}) (with parentheses, multi-value case)

The searchDatasets function passes the fq parameter directly to the CKAN API without normalization. While both formats may be valid in Solr/Lucene syntax, parenthesized filters are used for multi-value queries and client-side components. Standardize all single-value organization and group filters to use parentheses for consistency: (organization:${org._name}) and (groups:${group._name}).

pages/groups/[groupName].tsx (4)

12-12: Import added correctly.

The searchDatasets import properly supports the new server-side initial dataset fetching for groups.


27-27: Correct optimization: datasets fetched separately.

Setting include_datasets: false is appropriate since datasets are now fetched separately via searchDatasets for better performance and pagination support.


63-65: DatasetList integration looks good.

The updated DatasetList usage correctly passes type="group", the group name, and initialDatasets for server-side rendering with client-side pagination.


32-32: No CKAN API compatibility issues found.

CKAN's package_search fq parameter uses Apache Solr filter query syntax, where both groups:name and (groups:name) are semantically equivalent for single field filters. The variation in parentheses usage across pages/groups/[groupName].tsx (line 32), components/_shared/DatasetList.tsx (line 22), and lib/queries/dataset.ts (line 51) reflects stylistic differences rather than functional problems. Parentheses in Solr are only required when grouping multiple clauses or applying boolean operators, so the single-entity filters work correctly regardless of parentheses.

components/_shared/DatasetList.tsx (3)

20-22: fq filter syntax wrapped in parentheses.

The fq filter wraps the entire expression in parentheses (e.g., (organization:${name})), but the server-side calls in pages/[org]/index.tsx (line 39) and pages/groups/[groupName].tsx (line 32) don't use parentheses. Verify this doesn't cause inconsistent behavior with the CKAN API.

See comments in the page files for verification scripts to check fq parameter usage patterns.


59-73: Loading and empty states implemented correctly.

The component properly handles loading and empty states, providing good user feedback during data fetching and when no results are found.


75-95: Pagination integration looks good.

The Pagination component is properly integrated with smooth scrolling and conditional rendering based on the dataset count.

components/groups/individualPage/GroupInfo.tsx (1)

36-36: LGTM! Package count source updated correctly.

The change from group.packages?.length to group.package_count || 0 correctly aligns with the new data structure where package counts are provided directly rather than via array length.

}
})
);
console.log("Fetched organization:", org);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove debug console.log before merging.

This console.log statement appears to be debug code and should be removed before merging to production.

Apply this diff:

-  console.log("Fetched organization:", org);
-
📝 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
console.log("Fetched organization:", org);
🤖 Prompt for AI Agents
In pages/[org]/index.tsx around line 33, remove the debug console.log("Fetched
organization:", org); statement; delete that line from the file and ensure no
other stray console.debug/console.log calls remain in this component (replace
with proper app logger if persistent logging is required).

Comment on lines +35 to 50
let initialDatasets = null

if (org.package_count) {
initialDatasets = await searchDatasets({
fq: `organization:${org._name}`,
offset: 0,
limit: 10,
type: "dataset",
query: "",
sort: "metadata_modified desc",
groups: [],
orgs: [],
tags: [],
resFormat: [],
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add error handling for searchDatasets call.

The searchDatasets call lacks error handling. If the API call fails, it could cause the page to error or return unexpected data to the client.

Consider wrapping the call in a try-catch block:

   let initialDatasets = null
 
   if (org.package_count) {
-    initialDatasets = await searchDatasets({
-      fq: `organization:${org._name}`,
-      offset: 0,
-      limit: 10,
-      type: "dataset",
-      query: "",
-      sort: "metadata_modified desc",
-      groups: [],
-      orgs: [],
-      tags: [],
-      resFormat: [],
-    });
+    try {
+      initialDatasets = await searchDatasets({
+        fq: `organization:${org._name}`,
+        offset: 0,
+        limit: 10,
+        type: "dataset",
+        query: "",
+        sort: "metadata_modified desc",
+        groups: [],
+        orgs: [],
+        tags: [],
+        resFormat: [],
+      });
+    } catch (error) {
+      console.error("Failed to fetch initial datasets:", error);
+      // initialDatasets remains null, component will handle gracefully
+    }
   }
📝 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
let initialDatasets = null
if (org.package_count) {
initialDatasets = await searchDatasets({
fq: `organization:${org._name}`,
offset: 0,
limit: 10,
type: "dataset",
query: "",
sort: "metadata_modified desc",
groups: [],
orgs: [],
tags: [],
resFormat: [],
});
}
let initialDatasets = null
if (org.package_count) {
try {
initialDatasets = await searchDatasets({
fq: `organization:${org._name}`,
offset: 0,
limit: 10,
type: "dataset",
query: "",
sort: "metadata_modified desc",
groups: [],
orgs: [],
tags: [],
resFormat: [],
});
} catch (error) {
console.error("Failed to fetch initial datasets:", error);
// initialDatasets remains null, component will handle gracefully
}
}
🤖 Prompt for AI Agents
In pages/[org]/index.tsx around lines 35 to 50, the call to searchDatasets is
unprotected and can throw, so wrap the await searchDatasets(...) in a try-catch
block; on error log the error with context (e.g., include org._name), set
initialDatasets to a safe fallback (null or an empty array depending on
downstream expectations), and avoid rethrowing to prevent crashing the page (or
rethrow after logging if upstream should handle it).

Comment on lines +29 to 43
let initialDatasets = null;
if (group.package_count) {
initialDatasets = await searchDatasets({
fq: `groups:${group._name}`,
offset: 0,
limit: 10,
type: "dataset",
query: "",
sort: "metadata_modified desc",
groups: [],
orgs: [],
tags: [],
resFormat: [],
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add error handling for searchDatasets call.

The searchDatasets call lacks error handling. If the API call fails, it could cause the page to error or return unexpected data to the client.

Consider wrapping the call in a try-catch block:

   let initialDatasets = null;
   if (group.package_count) {
-    initialDatasets = await searchDatasets({
-      fq: `groups:${group._name}`,
-      offset: 0,
-      limit: 10,
-      type: "dataset",
-      query: "",
-      sort: "metadata_modified desc",
-      groups: [],
-      orgs: [],
-      tags: [],
-      resFormat: [],
-    });
+    try {
+      initialDatasets = await searchDatasets({
+        fq: `groups:${group._name}`,
+        offset: 0,
+        limit: 10,
+        type: "dataset",
+        query: "",
+        sort: "metadata_modified desc",
+        groups: [],
+        orgs: [],
+        tags: [],
+        resFormat: [],
+      });
+    } catch (error) {
+      console.error("Failed to fetch initial datasets:", error);
+      // initialDatasets remains null, component will handle gracefully
+    }
   }
📝 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
let initialDatasets = null;
if (group.package_count) {
initialDatasets = await searchDatasets({
fq: `groups:${group._name}`,
offset: 0,
limit: 10,
type: "dataset",
query: "",
sort: "metadata_modified desc",
groups: [],
orgs: [],
tags: [],
resFormat: [],
});
}
let initialDatasets = null;
if (group.package_count) {
try {
initialDatasets = await searchDatasets({
fq: `groups:${group._name}`,
offset: 0,
limit: 10,
type: "dataset",
query: "",
sort: "metadata_modified desc",
groups: [],
orgs: [],
tags: [],
resFormat: [],
});
} catch (error) {
console.error("Failed to fetch initial datasets:", error);
// initialDatasets remains null, component will handle gracefully
}
}
🤖 Prompt for AI Agents
In pages/groups/[groupName].tsx around lines 29 to 43, the call to
searchDatasets has no error handling; wrap the await searchDatasets(...) call in
a try-catch, on error log the error (use existing logger or console.error), and
set initialDatasets to a safe fallback (null or an empty result structure) so
the page can render instead of crashing; optionally rethrow or report to
monitoring only if you want build-time failure.

Copy link
Member

@demenech demenech left a comment

Choose a reason for hiding this comment

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

LGTM

@demenech demenech merged commit 9374be5 into main Nov 3, 2025
3 checks passed
@demenech demenech deleted the datasetlist branch November 3, 2025 19:41
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.

3 participants