feat(skills): improve marketplace filters and server pagination
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
Reference in New Issue
Block a user