feat(skills): improve marketplace filters and server pagination

This commit is contained in:
Abhimanyu Saharan
2026-02-14 13:09:48 +05:30
parent e7d47d9f8a
commit 823da1d143
3 changed files with 45 additions and 32 deletions

View File

@@ -717,7 +717,7 @@ def _as_card(
skill: MarketplaceSkill, skill: MarketplaceSkill,
installation: GatewayInstalledSkill | None, installation: GatewayInstalledSkill | None,
) -> MarketplaceSkillCardRead: ) -> MarketplaceSkillCardRead:
card_source = skill.source_url card_source: str | None = skill.source_url
if not card_source: if not card_source:
card_source = skill.source card_source = skill.source
@@ -966,8 +966,7 @@ async def list_marketplace_skills(
) )
else: else:
skills_query = skills_query.filter( skills_query = skills_query.filter(
func.lower(func.trim(col(MarketplaceSkill.category))) func.lower(func.trim(col(MarketplaceSkill.category))) == normalized_category,
== normalized_category,
) )
normalized_risk = (risk or "").strip().lower() normalized_risk = (risk or "").strip().lower()

View File

@@ -177,7 +177,9 @@ export default function SkillsMarketplacePage() {
const [selectedCategory, setSelectedCategory] = useState<string>( const [selectedCategory, setSelectedCategory] = useState<string>(
initialCategory || "all", initialCategory || "all",
); );
const [selectedRisk, setSelectedRisk] = useState<string>(initialRisk || "safe"); const [selectedRisk, setSelectedRisk] = useState<string>(
initialRisk || "safe",
);
const [currentPage, setCurrentPage] = useState(initialPage); const [currentPage, setCurrentPage] = useState(initialPage);
const [pageSize, setPageSize] = useState(initialPageSize); const [pageSize, setPageSize] = useState(initialPageSize);
@@ -261,34 +263,29 @@ export default function SkillsMarketplacePage() {
const skillsQuery = useListMarketplaceSkillsApiV1SkillsMarketplaceGet< const skillsQuery = useListMarketplaceSkillsApiV1SkillsMarketplaceGet<
listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse, listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse,
ApiError ApiError
>( >(skillsParams, {
skillsParams,
{
query: { query: {
enabled: Boolean(isSignedIn && isAdmin && resolvedGatewayId), enabled: Boolean(isSignedIn && isAdmin && resolvedGatewayId),
refetchOnMount: "always", refetchOnMount: "always",
refetchInterval: 15_000, refetchInterval: 15_000,
}, },
}, });
);
const skills = useMemo<MarketplaceSkillCardRead[]>( const skills = useMemo<MarketplaceSkillCardRead[]>(
() => (skillsQuery.data?.status === 200 ? skillsQuery.data.data : []), () => (skillsQuery.data?.status === 200 ? skillsQuery.data.data : []),
[skillsQuery.data], [skillsQuery.data],
); );
const filterOptionSkillsQuery = useListMarketplaceSkillsApiV1SkillsMarketplaceGet< const filterOptionSkillsQuery =
useListMarketplaceSkillsApiV1SkillsMarketplaceGet<
listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse, listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse,
ApiError ApiError
>( >(filterOptionsParams, {
filterOptionsParams,
{
query: { query: {
enabled: Boolean(isSignedIn && isAdmin && resolvedGatewayId), enabled: Boolean(isSignedIn && isAdmin && resolvedGatewayId),
refetchOnMount: "always", refetchOnMount: "always",
refetchInterval: 15_000, refetchInterval: 15_000,
}, },
}, });
);
const filterOptionSkills = useMemo<MarketplaceSkillCardRead[]>( const filterOptionSkills = useMemo<MarketplaceSkillCardRead[]>(
() => () =>
filterOptionSkillsQuery.data?.status === 200 filterOptionSkillsQuery.data?.status === 200
@@ -322,7 +319,10 @@ export default function SkillsMarketplacePage() {
return { hasKnownTotal: false, total: skills.length }; return { hasKnownTotal: false, total: skills.length };
} }
const totalCountHeader = skillsQuery.data.headers.get("x-total-count"); const totalCountHeader = skillsQuery.data.headers.get("x-total-count");
if (typeof totalCountHeader === "string" && totalCountHeader.trim() !== "") { if (
typeof totalCountHeader === "string" &&
totalCountHeader.trim() !== ""
) {
const parsed = Number(totalCountHeader); const parsed = Number(totalCountHeader);
if (Number.isFinite(parsed) && parsed >= 0) { if (Number.isFinite(parsed) && parsed >= 0) {
return { hasKnownTotal: true, total: parsed }; return { hasKnownTotal: true, total: parsed };
@@ -345,13 +345,16 @@ export default function SkillsMarketplacePage() {
return currentPage < totalPages; return currentPage < totalPages;
} }
return skills.length === pageSize; return skills.length === pageSize;
}, [currentPage, pageSize, skills.length, totalCountInfo.hasKnownTotal, totalPages]); }, [
const rangeStart = currentPage,
totalSkills === 0 ? 0 : (currentPage - 1) * pageSize + 1; pageSize,
skills.length,
totalCountInfo.hasKnownTotal,
totalPages,
]);
const rangeStart = totalSkills === 0 ? 0 : (currentPage - 1) * pageSize + 1;
const rangeEnd = const rangeEnd =
totalSkills === 0 totalSkills === 0 ? 0 : (currentPage - 1) * pageSize + skills.length;
? 0
: (currentPage - 1) * pageSize + skills.length;
const categoryFilterOptions = useMemo(() => { const categoryFilterOptions = useMemo(() => {
const byValue = new Map<string, string>(); const byValue = new Map<string, string>();
@@ -819,7 +822,10 @@ export default function SkillsMarketplacePage() {
<SelectContent> <SelectContent>
<SelectItem value="all">All categories</SelectItem> <SelectItem value="all">All categories</SelectItem>
{categoryFilterOptions.map((category) => ( {categoryFilterOptions.map((category) => (
<SelectItem key={category.value} value={category.value}> <SelectItem
key={category.value}
value={category.value}
>
{category.label} {category.label}
</SelectItem> </SelectItem>
))} ))}
@@ -833,7 +839,10 @@ export default function SkillsMarketplacePage() {
> >
Risk Risk
</label> </label>
<Select value={selectedRisk} onValueChange={setSelectedRisk}> <Select
value={selectedRisk}
onValueChange={setSelectedRisk}
>
<SelectTrigger <SelectTrigger
id="marketplace-risk-filter" id="marketplace-risk-filter"
className="h-11" className="h-11"
@@ -917,7 +926,9 @@ export default function SkillsMarketplacePage() {
variant="ghost" variant="ghost"
size="sm" size="sm"
disabled={currentPage <= 1 || skillsQuery.isLoading} disabled={currentPage <= 1 || skillsQuery.isLoading}
onClick={() => setCurrentPage((prev) => Math.max(1, prev - 1))} onClick={() =>
setCurrentPage((prev) => Math.max(1, prev - 1))
}
> >
Previous Previous
</Button> </Button>

View File

@@ -80,7 +80,10 @@ function riskBadgeLabel(risk: string | null | undefined) {
type MarketplaceSkillsTableProps = { type MarketplaceSkillsTableProps = {
skills: MarketplaceSkillCardRead[]; skills: MarketplaceSkillCardRead[];
installedGatewayNamesBySkillId?: Record<string, { id: string; name: string }[]>; installedGatewayNamesBySkillId?: Record<
string,
{ id: string; name: string }[]
>;
isLoading?: boolean; isLoading?: boolean;
sorting?: SortingState; sorting?: SortingState;
onSortingChange?: OnChangeFn<SortingState>; onSortingChange?: OnChangeFn<SortingState>;