feat: scope mobile search by type and status (Closes #793)
This commit is contained in:
1 parent
3c65bc1b31
commit
bd054998b2
14 files changed
+315
-37
No files matched your search
@@ -28,7 +28,7 @@ FEATURE_SOURCES = {
|
||||
"device-setup": ("static/install-app.js", "static/mobile-device-setup.js"),
|
||||
"security-center": ("static/security-center.js",),
|
||||
"today-timer": (
|
||||
"static/task-overlay-history.js", "static/search-preview.js", "static/my-work.js", "static/protect-today.js", "static/mobile-task-dock.js", "static/mobile-queue-launcher.js", "static/update-triage-session.js", "static/update-review-handoff.js", "static/update-triage-launcher.js", "static/update-triage-gesture.js", "static/notification-undo.js", "static/today-timer.js", "static/today-recap.js",
|
||||
"static/commands.js", "static/task-overlay-history.js", "static/search-preview.js", "static/my-work.js", "static/protect-today.js", "static/mobile-task-dock.js", "static/mobile-queue-launcher.js", "static/update-triage-session.js", "static/update-review-handoff.js", "static/update-triage-launcher.js", "static/update-triage-gesture.js", "static/notification-undo.js", "static/today-timer.js", "static/today-recap.js",
|
||||
"static/today-rollover.js", "static/later-work.js", "static/drafts.js", "static/unfiled-captures.js",
|
||||
"static/draft-filing-session.js", "static/draft-capacity-dialog.js", "static/work-selection.js",
|
||||
"static/today-work.js", "static/pick-work.js", "static/batch-find-work.js",
|
||||
|
||||
+21
-9
@@ -463,14 +463,27 @@ def _normalize_global_search_item(item: Any, kind: str) -> dict | None:
|
||||
}
|
||||
|
||||
|
||||
async def global_search(query: str, limit: int = 10, page: int = 1) -> dict:
|
||||
async def global_search(
|
||||
query: str,
|
||||
limit: int = 10,
|
||||
page: int = 1,
|
||||
kind: str = "all",
|
||||
state: str = "all",
|
||||
) -> dict:
|
||||
"""Search accessible issues and pulls concurrently with balanced pagination."""
|
||||
stream_limit = (limit + 1) // 2
|
||||
item_types = ("issues", "pulls") if kind == "all" else (
|
||||
"issues" if kind == "issue" else "pulls",
|
||||
)
|
||||
stream_limit = (limit + 1) // 2 if kind == "all" else limit
|
||||
async def load(item_type: str) -> Any:
|
||||
params = {
|
||||
"q": query, "type": item_type, "state": state,
|
||||
"limit": stream_limit, "page": page,
|
||||
}
|
||||
response = await _get_client().get(
|
||||
"/api/v1/repos/issues/search",
|
||||
headers=_auth(),
|
||||
params={"q": query, "type": item_type, "limit": stream_limit, "page": page},
|
||||
params=params,
|
||||
)
|
||||
response.raise_for_status()
|
||||
payload = response.json()
|
||||
@@ -478,9 +491,7 @@ async def global_search(query: str, limit: int = 10, page: int = 1) -> dict:
|
||||
raise ValueError("Gitea global search response was not a list")
|
||||
return payload
|
||||
|
||||
outcomes = await asyncio.gather(
|
||||
load("issues"), load("pulls"), return_exceptions=True
|
||||
)
|
||||
outcomes = await asyncio.gather(*(load(item_type) for item_type in item_types), return_exceptions=True)
|
||||
for outcome in outcomes:
|
||||
if isinstance(outcome, asyncio.CancelledError):
|
||||
raise outcome
|
||||
@@ -489,15 +500,16 @@ async def global_search(query: str, limit: int = 10, page: int = 1) -> dict:
|
||||
|
||||
streams: list[list[dict]] = []
|
||||
seen: set[tuple[str, str, int]] = set()
|
||||
for outcome, kind in zip(outcomes, ("issue", "pull"), strict=True):
|
||||
result_kinds = tuple("issue" if item_type == "issues" else "pull" for item_type in item_types)
|
||||
for outcome, result_kind in zip(outcomes, result_kinds, strict=True):
|
||||
normalized_stream = []
|
||||
if isinstance(outcome, BaseException):
|
||||
streams.append(normalized_stream)
|
||||
continue
|
||||
for item in outcome:
|
||||
normalized = _normalize_global_search_item(item, kind)
|
||||
normalized = _normalize_global_search_item(item, result_kind)
|
||||
if normalized is not None:
|
||||
identity = (kind, normalized["repository"], normalized["number"])
|
||||
identity = (result_kind, normalized["repository"], normalized["number"])
|
||||
if identity in seen:
|
||||
continue
|
||||
seen.add(identity)
|
||||
|
||||
+17
-6
@@ -1006,11 +1006,15 @@ def _share_target_login_redirect(request: Request) -> str:
|
||||
search_values = request.query_params.getlist("search")
|
||||
preview_values = request.query_params.getlist("preview")
|
||||
if search_values or preview_values:
|
||||
allowed = {"search", "preview"}
|
||||
allowed = {"search", "preview", "search_kind", "search_state"}
|
||||
kind_values = request.query_params.getlist("search_kind")
|
||||
state_values = request.query_params.getlist("search_state")
|
||||
if (
|
||||
set(request.query_params.keys()) - allowed
|
||||
or len(search_values) != 1
|
||||
or len(preview_values) != 1
|
||||
or len(kind_values) > 1
|
||||
or len(state_values) > 1
|
||||
or len(search_values[0]) > 200
|
||||
or len(preview_values[0]) > 200
|
||||
or not re.fullmatch(
|
||||
@@ -1019,9 +1023,14 @@ def _share_target_login_redirect(request: Request) -> str:
|
||||
)
|
||||
):
|
||||
return "login"
|
||||
continuation = urlencode(
|
||||
(("search", search_values[0].strip()), ("preview", preview_values[0]))
|
||||
)
|
||||
continuation_values = [
|
||||
("search", search_values[0].strip()), ("preview", preview_values[0]),
|
||||
]
|
||||
if kind_values and kind_values[0] in {"all", "issue", "pull"}:
|
||||
continuation_values.append(("search_kind", kind_values[0]))
|
||||
if state_values and state_values[0] in {"all", "open", "closed"}:
|
||||
continuation_values.append(("search_state", state_values[0]))
|
||||
continuation = urlencode(continuation_values)
|
||||
return f"login?{urlencode({'continue': f'./?{continuation}'})}"
|
||||
limits = {"title": 200, "text": 8000, "url": 2048}
|
||||
if any(
|
||||
@@ -2712,13 +2721,15 @@ 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),
|
||||
kind: Literal["all", "issue", "pull"] = Query(default="all"),
|
||||
state: Literal["all", "open", "closed"] = Query(default="all"),
|
||||
) -> 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, page),
|
||||
gitea_proxy.global_search(query, limit, page, kind, state),
|
||||
timeout=GLOBAL_SEARCH_TIMEOUT_SECONDS,
|
||||
)
|
||||
except Exception:
|
||||
@@ -2727,7 +2738,7 @@ async def global_search(
|
||||
status_code=503,
|
||||
headers={"Retry-After": "1"},
|
||||
)
|
||||
return JSONResponse({"query": query, **result})
|
||||
return JSONResponse({"query": query, "scope": {"kind": kind, "state": state}, **result})
|
||||
|
||||
|
||||
@app.get("/api/v1/repos/{owner}/{repo}/issues/{number}/preview")
|
||||
|
||||
Reference in new issue
Block a user