feat: paginate balanced mobile search results (Closes #783)
CI / lint (pull_request) Successful in 1m34s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-14 00:04:06 +00:00
1 parent fa3e5e3bf0
commit 7b736bc4c6
8 files changed
+149 -45

No files matched your search

+19 -5
View File
@@ -463,13 +463,14 @@ def _normalize_global_search_item(item: Any, kind: str) -> dict | None:
}
async def global_search(query: str, limit: int = 10) -> dict:
"""Search accessible issues and pulls concurrently with a bounded result set."""
async def global_search(query: str, limit: int = 10, page: int = 1) -> dict:
"""Search accessible issues and pulls concurrently with balanced pagination."""
stream_limit = (limit + 1) // 2
async def load(item_type: str) -> Any:
response = await _get_client().get(
"/api/v1/repos/issues/search",
headers=_auth(),
params={"q": query, "type": item_type, "limit": limit, "page": 1},
params={"q": query, "type": item_type, "limit": stream_limit, "page": page},
)
response.raise_for_status()
payload = response.json()
@@ -486,10 +487,12 @@ async def global_search(query: str, limit: int = 10) -> dict:
if all(isinstance(outcome, BaseException) for outcome in outcomes):
raise outcomes[0]
results = []
streams: list[list[dict]] = []
seen: set[tuple[str, str, int]] = set()
for outcome, kind in zip(outcomes, ("issue", "pull"), strict=True):
normalized_stream = []
if isinstance(outcome, BaseException):
streams.append(normalized_stream)
continue
for item in outcome:
normalized = _normalize_global_search_item(item, kind)
@@ -498,10 +501,21 @@ async def global_search(query: str, limit: int = 10) -> dict:
if identity in seen:
continue
seen.add(identity)
results.append(normalized)
normalized_stream.append(normalized)
streams.append(normalized_stream)
results = []
for index in range(max((len(stream) for stream in streams), default=0)):
for stream in streams:
if index < len(stream):
results.append(stream[index])
return {
"items": results[:limit],
"partial": any(isinstance(outcome, BaseException) for outcome in outcomes),
"has_more": any(
not isinstance(outcome, BaseException) and len(outcome) >= stream_limit
for outcome in outcomes
),
"next_page": page + 1,
}
+2 -1
View File
@@ -2690,13 +2690,14 @@ async def background_identity() -> JSONResponse:
async def global_search(
q: str = Query(min_length=2, max_length=100),
limit: int = Query(default=10, ge=1, le=25),
page: int = Query(default=1, ge=1, le=100),
) -> JSONResponse:
query = q.strip()
if len(query) < 2:
raise HTTPException(status_code=422, detail="Search query must contain at least 2 characters")
try:
result = await asyncio.wait_for(
gitea_proxy.global_search(query, limit),
gitea_proxy.global_search(query, limit, page),
timeout=GLOBAL_SEARCH_TIMEOUT_SECONDS,
)
except Exception: