Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 88 additions & 7 deletions components/_shared/DatasetList.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,96 @@
import { useState } from "react";
import { Dataset } from "@portaljs/ckan";
import useSWR from "swr";
import DatasetItem from "../dataset/search/DatasetItem";
import Pagination from "./PaginationOrgGroup";
import { searchDatasets } from "@/lib/queries/dataset";


interface DatasetListProps {
datasets: Array<Dataset>;
type: "organization" | "group";
name: string;
initialDatasets?: any;
}
export default function DatasetList({ datasets }: DatasetListProps) {

export default function DatasetList({ type, name, initialDatasets }: DatasetListProps) {
const [offset, setOffset] = useState(0);
const [subsetOfPages, setSubsetOfPages] = useState(0);
const limit = 10;

const fq = type === "organization"
? `(organization:${name})`
: `(groups:${name})`;

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: [],
});
},
{
fallbackData: initialDatasets || undefined,
revalidateOnFocus: false,
}
);

const datasets = searchResults?.results || [];
const count = searchResults?.count || 0;

const handlePageChange = (newOffset: number) => {
setOffset(newOffset);
if (typeof window !== "undefined") {
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
};

if (isValidating && datasets.length === 0) {
return (
<div className="py-8 w-full flex justify-center">
<span className="text-gray-500">Loading datasets...</span>
</div>
);
}

if (!isValidating && datasets.length === 0) {
return (
<div className="py-8 w-full flex justify-center">
<span className="text-gray-500">No datasets found.</span>
</div>
);
}

return (
<div className="py-8 w-full max-h-[600px] flex flex-col gap-y-4">
{datasets.map((dataset: Dataset) => (
<DatasetItem key={dataset.id} dataset={dataset} />
))}
<div className="py-8 w-full flex flex-col gap-y-4">
<div className="flex flex-col gap-y-4">
{datasets.map((dataset: Dataset) => (
<DatasetItem key={dataset.id} dataset={dataset} />
))}
</div>

{count > limit && (
<div className="flex justify-center mt-6">
<Pagination
subsetOfPages={subsetOfPages}
setSubsetOfPages={setSubsetOfPages}
count={count}
offset={offset}
onPageChange={handlePageChange}
/>
</div>
)}
</div>
);
}
}
92 changes: 92 additions & 0 deletions components/_shared/PaginationOrgGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Dispatch, SetStateAction } from "react";

interface PaginationProps {
subsetOfPages: number;
setSubsetOfPages: Dispatch<SetStateAction<number>>;
count: number;
offset?: number;
onPageChange?: (newOffset: number) => void;
}

export default function Pagination({
subsetOfPages,
setSubsetOfPages,
count,
offset,
onPageChange,
}: PaginationProps) {
const max = 10;
const currentPage = offset !== undefined ? offset / max : 0;

const handlePageClick = (pageIndex: number) => {
if (onPageChange) {
onPageChange(pageIndex * max);
}
};

return (
<div className="flex gap-2 align-center">
{subsetOfPages !== 0 && (
<button
className="font-semibold flex items-center gap-2"
onClick={() => setSubsetOfPages(subsetOfPages - max)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="19"
height="16"
viewBox="0 0 19 16"
fill="none"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.15219 15.248C8.68844 15.6664 7.93656 15.6664 7.47281 15.248L0.34781 8.81938C0.12511 8.61845 -6.30829e-07 8.34593 -6.55671e-07 8.06177C-6.80513e-07 7.77761 0.12511 7.50509 0.34781 7.30415L7.47281 0.875584C7.93656 0.457164 8.68844 0.457164 9.15219 0.875584C9.61594 1.294 9.61594 1.97239 9.15219 2.39081L4.05438 6.99034L17.8125 6.99034C18.4683 6.99034 19 7.47003 19 8.06177C19 8.6535 18.4683 9.13319 17.8125 9.1332L4.05438 9.1332L9.15219 13.7327C9.61594 14.1511 9.61594 14.8295 9.15219 15.248Z"
fill="#AAAAAA"
/>
</svg>
<span className="text-[#757575] text-[18px]">Prev</span>
</button>
)}
{Array.from(Array(Math.ceil(count / max)).keys()).map((x) => (
<button
key={x}
className={`${
x === currentPage
? "bg-accent-100 !h-9 !w-9 rounded-[10px] text-black"
: ""
} px-2 font-semibold`}
onClick={() => handlePageClick(x)}
style={{
display:
x >= subsetOfPages && x < subsetOfPages + max ? "block" : "none",
}}
>
{x + 1}
</button>
))}
{count > max * max && (subsetOfPages + max) * max < count && (
<button
className="font-semibold flex items-center gap-2"
onClick={() => setSubsetOfPages(subsetOfPages + max)}
>
<span className="text-[18px] text-[#313131]">Next</span>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"
/>
</svg>
</button>
)}
</div>
);
}
2 changes: 1 addition & 1 deletion components/groups/individualPage/GroupInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function GroupInfo({ group }: { group: Group }) {
d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776"
/>
</svg>
Packages: {group.packages ? group.packages.length : 0}
Packages: {group.package_count || 0}
</span>
<span className="font-medium text-gray-500 inline">
<svg
Expand Down
2 changes: 1 addition & 1 deletion components/organization/individualPage/OrgInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default function OrgInfo({ org }: { org: Organization }) {
d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776"
/>
</svg>
Packages: {org.packages ? org.packages.length : 0}
Packages: {org.package_count || 0}
</span>
<span className="font-medium text-gray-500 inline">
<svg
Expand Down
48 changes: 24 additions & 24 deletions pages/[org]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import DatasetList from "@/components/_shared/DatasetList";
import { CKAN } from "@portaljs/ckan";
import { getOrganization } from "@/lib/queries/orgs";
import { getDataset } from "@/lib/queries/dataset";
import { searchDatasets } from "@/lib/queries/dataset";

import HeroSection from "@/components/_shared/HeroSection";
import { OrganizationIndividualPageStructuredData } from "@/components/schema/OrganizationIndividualPageStructuredData";
Expand All @@ -26,26 +27,26 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
orgName = orgName.split("@")[1];
let org = await getOrganization({
name: orgName as string,
include_datasets: true,
include_datasets: false,
});

if (org.packages) {
const packagesWithResources = await Promise.all(
org.packages.map(async (dataset) => {
try {
const fullDataset = await getDataset({ name: dataset.name });
return fullDataset || null;
} catch (err) {
console.error(`Failed to fetch dataset: ${dataset.name}`, err);
return null;
}
})
);
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).


org = {
...org,
packages: packagesWithResources.filter(Boolean),
};
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: [],
});
}
Comment on lines +35 to 50
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).


const activityStream = await ckan.getOrgActivityStream(org._name);
Expand All @@ -54,27 +55,26 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
notFound: true,
};
}
org = { ...org, activity_stream: activityStream };
org = { ...org, activity_stream: activityStream};
return {
props: {
org,
initialDatasets,
},
};
};

export default function OrgPage({ org }): JSX.Element {
export default function OrgPage({ org, initialDatasets }): JSX.Element {
const tabs = [
{
id: "datasets",
content: org.packages ? (
<DatasetList datasets={org.packages ? org.packages : []} />
) : (
""
content: (
<DatasetList type="organization" name={org._name} initialDatasets={initialDatasets} />
),
title: "Datasets",
},
{
id: "activity-stream",
id: "activity-stream",
content: (
<ActivityStream
activities={org?.activity_stream ? org.activity_stream : []}
Expand Down
34 changes: 21 additions & 13 deletions pages/groups/[groupName].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import styles from "@/styles/DatasetInfo.module.scss";
import GroupNavCrumbs from "../../components/groups/individualPage/GroupNavCrumbs";
import GroupInfo from "../../components/groups/individualPage/GroupInfo";
import { getGroup } from "@/lib/queries/groups";
import { getDataset } from "@/lib/queries/dataset";
import { getDataset, searchDatasets } from "@/lib/queries/dataset";
import HeroSection from "@/components/_shared/HeroSection";
import { GroupIndividualPageStructuredData } from "@/components/schema/GroupIndividualPageStructuredData";

Expand All @@ -24,13 +24,22 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
}
let group = await getGroup({
name: groupName as string,
include_datasets: true,
include_datasets: false,
});
if (group.packages) {
const packagesWithResources = await Promise.all(
group.packages.map(async (dataset) => getDataset({ name: dataset.name }))
);
group = { ...group, packages: packagesWithResources };
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: [],
});
}
Comment on lines +29 to 43
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.

const activityStream = await ckan.getGroupActivityStream(group._name);
if (!group) {
Expand All @@ -42,24 +51,23 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
return {
props: {
group,
initialDatasets,
},
};
};

export default function GroupPage({ group }): JSX.Element {
export default function GroupPage({ group, initialDatasets }): JSX.Element {
const tabs = [
{
id: "datasets",
content: group.packages ? (
<DatasetList datasets={group.packages ? group.packages : []} />
) : (
""
content: (
<DatasetList type="group" name={group._name} initialDatasets={initialDatasets} />
),
title: "Datasets",
},
{
id: "activity-stream",
content: (
content: (
<ActivityStream
activities={group?.activity_stream ? group.activity_stream : []}
/>
Expand Down