fix(frontend): Fix marketplace sort by (#11284)

Marketplace sort by functionality was not working on the frontend. This
PR fixes it

### Changes 🏗️

- Add type hints for sort by
- Fix marketplace sort by drop downs


### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  <!-- Put your test plan here: -->
  - [x] tested locally
This commit is contained in:
Swifty
2025-10-30 09:46:11 +01:00
committed by GitHub
parent cab6590908
commit 594b1adcf7
10 changed files with 65 additions and 40 deletions

View File

@@ -170,7 +170,7 @@ class SearchStoreAgentsBlock(Block):
category: str | None = SchemaField(
description="Filter by category", default=None
)
sort_by: Literal["rating", "runs", "name", "recent"] = SchemaField(
sort_by: Literal["rating", "runs", "name", "updated_at"] = SchemaField(
description="How to sort the results", default="rating"
)
limit: int = SchemaField(
@@ -272,24 +272,18 @@ class SearchStoreAgentsBlock(Block):
self,
query: str | None = None,
category: str | None = None,
sort_by: str = "rating",
sort_by: Literal["rating", "runs", "name", "updated_at"] = "rating",
limit: int = 10,
) -> SearchAgentsResponse:
"""
Search for agents in the store using the existing store database function.
"""
# Map our sort_by to the store's sorted_by parameter
sorted_by_map = {
"rating": "most_popular",
"runs": "most_runs",
"name": "alphabetical",
"recent": "recently_updated",
}
result = await get_database_manager_async_client().get_store_agents(
featured=False,
creators=None,
sorted_by=sorted_by_map.get(sort_by, "most_popular"),
sorted_by=sort_by,
search_query=query,
category=category,
page=1,

View File

@@ -1,3 +1,5 @@
from typing import Literal
import backend.server.v2.store.db
from backend.util.cache import cached
@@ -20,7 +22,7 @@ def clear_all_caches():
async def _get_cached_store_agents(
featured: bool,
creator: str | None,
sorted_by: str | None,
sorted_by: Literal["rating", "runs", "name", "updated_at"] | None,
search_query: str | None,
category: str | None,
page: int,
@@ -52,7 +54,7 @@ async def _get_cached_agent_details(username: str, agent_name: str):
async def _get_cached_store_creators(
featured: bool,
search_query: str | None,
sorted_by: str | None,
sorted_by: Literal["agent_rating", "agent_runs", "num_agents"] | None,
page: int,
page_size: int,
):

View File

@@ -2,6 +2,7 @@ import asyncio
import logging
import typing
from datetime import datetime, timezone
from typing import Literal
import fastapi
import prisma.enums
@@ -41,7 +42,7 @@ DEFAULT_ADMIN_EMAIL = "admin@autogpt.co"
async def get_store_agents(
featured: bool = False,
creators: list[str] | None = None,
sorted_by: str | None = None,
sorted_by: Literal["rating", "runs", "name", "updated_at"] | None = None,
search_query: str | None = None,
category: str | None = None,
page: int = 1,
@@ -63,7 +64,7 @@ async def get_store_agents(
ALLOWED_ORDER_BY = {
"rating": "rating DESC, rank DESC",
"runs": "runs DESC, rank DESC",
"name": "agent_name ASC, rank DESC",
"name": "agent_name ASC, rank ASC",
"updated_at": "updated_at DESC, rank DESC",
}
@@ -421,7 +422,7 @@ async def get_store_agent_by_version_id(
async def get_store_creators(
featured: bool = False,
search_query: str | None = None,
sorted_by: str | None = None,
sorted_by: Literal["agent_rating", "agent_runs", "num_agents"] | None = None,
page: int = 1,
page_size: int = 20,
) -> backend.server.v2.store.model.CreatorsResponse:

View File

@@ -392,20 +392,6 @@ async def test_get_store_agents_with_search_and_filters_parameterized():
assert isinstance(result.agents, list)
@pytest.mark.asyncio(loop_scope="session")
async def test_get_store_agents_search_with_invalid_sort_by():
"""Test that invalid sorted_by value doesn't cause SQL injection""" # Try to inject SQL via sorted_by parameter
malicious_sort = "rating; DROP TABLE Users; --"
result = await db.get_store_agents(
search_query="test",
sorted_by=malicious_sort,
)
# Verify the query executed without error
# Invalid sort_by should fall back to default, not cause SQL injection
assert isinstance(result.agents, list)
@pytest.mark.asyncio(loop_scope="session")
async def test_get_store_agents_search_category_array_injection():
"""Test that category parameter is safely passed as a parameter"""

View File

@@ -2,6 +2,7 @@ import logging
import tempfile
import typing
import urllib.parse
from typing import Literal
import autogpt_libs.auth
import fastapi
@@ -93,7 +94,7 @@ async def update_or_create_profile(
async def get_agents(
featured: bool = False,
creator: str | None = None,
sorted_by: str | None = None,
sorted_by: Literal["rating", "runs", "name", "updated_at"] | None = None,
search_query: str | None = None,
category: str | None = None,
page: int = 1,
@@ -254,7 +255,7 @@ async def create_review(
async def get_creators(
featured: bool = False,
search_query: str | None = None,
sorted_by: str | None = None,
sorted_by: Literal["agent_rating", "agent_runs", "num_agents"] | None = None,
page: int = 1,
page_size: int = 20,
):

View File

@@ -7,13 +7,16 @@ import { Separator } from "@/components/__legacy__/ui/separator";
import { FeaturedCreators } from "../FeaturedCreators/FeaturedCreators";
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
import { MainMarketplacePageLoading } from "../MainMarketplacePageLoading";
import { GetV2ListStoreAgentsParams } from "@/app/api/__generated__/models/getV2ListStoreAgentsParams";
type MarketplaceSearchSort = GetV2ListStoreAgentsParams["sorted_by"];
export const MainSearchResultPage = ({
searchTerm,
sort,
}: {
searchTerm: string;
sort: string;
sort: MarketplaceSearchSort;
}) => {
const {
agents,

View File

@@ -3,12 +3,17 @@ import {
useGetV2ListStoreCreators,
} from "@/app/api/__generated__/endpoints/store/store";
import { CreatorsResponse } from "@/app/api/__generated__/models/creatorsResponse";
import { GetV2ListStoreAgentsParams } from "@/app/api/__generated__/models/getV2ListStoreAgentsParams";
import { GetV2ListStoreCreatorsParams } from "@/app/api/__generated__/models/getV2ListStoreCreatorsParams";
import { StoreAgentsResponse } from "@/app/api/__generated__/models/storeAgentsResponse";
import { useState, useMemo } from "react";
type MarketplaceSearchSort = GetV2ListStoreAgentsParams["sorted_by"];
type CreatorSortBy = GetV2ListStoreCreatorsParams["sorted_by"];
interface useMainSearchResultPageType {
searchTerm: string;
sort: string;
sort: MarketplaceSearchSort;
}
export const useMainSearchResultPage = ({
@@ -17,7 +22,9 @@ export const useMainSearchResultPage = ({
}: useMainSearchResultPageType) => {
const [showAgents, setShowAgents] = useState(true);
const [showCreators, setShowCreators] = useState(true);
const [clientSortBy, setClientSortBy] = useState<string>(sort);
const [clientSortBy, setClientSortBy] = useState<string>(
sort ?? "updated_at",
);
const {
data: agentsData,
@@ -37,12 +44,25 @@ export const useMainSearchResultPage = ({
},
);
const creatorsSortBy: CreatorSortBy = useMemo(() => {
switch (sort) {
case "runs":
return "agent_runs";
case "rating":
return "agent_rating";
default:
return "num_agents";
}
}, [sort]);
const {
data: creatorsData,
isLoading: isCreatorsLoading,
isError: isCreatorsError,
} = useGetV2ListStoreCreators(
{ search_query: searchTerm, sorted_by: sort },
{
search_query: searchTerm,
sorted_by: creatorsSortBy,
},
{
query: {
select: (x) => {

View File

@@ -2,8 +2,13 @@
import { use } from "react";
import { MainSearchResultPage } from "../components/MainSearchResultPage/MainSearchResultPage";
import { GetV2ListStoreAgentsParams } from "@/app/api/__generated__/models/getV2ListStoreAgentsParams";
type MarketplaceSearchPageSearchParams = { searchTerm?: string; sort?: string };
type MarketplaceSearchSort = GetV2ListStoreAgentsParams["sorted_by"];
type MarketplaceSearchPageSearchParams = {
searchTerm?: string;
sort?: MarketplaceSearchSort;
};
export default function MarketplaceSearchPage({
searchParams,
@@ -13,7 +18,7 @@ export default function MarketplaceSearchPage({
return (
<MainSearchResultPage
searchTerm={use(searchParams).searchTerm || ""}
sort={use(searchParams).sort || "trending"}
sort={use(searchParams).sort || "runs"}
/>
);
}

View File

@@ -2580,7 +2580,13 @@
"in": "query",
"required": false,
"schema": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"anyOf": [
{
"enum": ["rating", "runs", "name", "updated_at"],
"type": "string"
},
{ "type": "null" }
],
"title": "Sorted By"
}
},
@@ -2830,7 +2836,13 @@
"in": "query",
"required": false,
"schema": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"anyOf": [
{
"enum": ["agent_rating", "agent_runs", "num_agents"],
"type": "string"
},
{ "type": "null" }
],
"title": "Sorted By"
}
},

View File

@@ -10,9 +10,10 @@ import {
import { ChevronDownIcon } from "@radix-ui/react-icons";
const sortOptions: SortOption[] = [
// { label: "Most Recent", value: "recent" }, // we are not using this for now because we don't have date data from the backend
{ label: "Most Runs", value: "runs" },
// { label: "Highest Rated", value: "rating" }, // we are not using this for now because we don't have rating data from the backend
{ label: "Highest Rated", value: "rating" },
{ label: "Name (A-Z)", value: "name" },
{ label: "Recently Updated", value: "updated_at" },
];
interface SortOption {