feat(ticket): 列表支持按标题/正文搜索
手机端工单列表要能像任务、知识一样搜。与知识列表同一套 ilike 匹配,标题记不清时正文关键词也能命中;补测试覆盖标题命中、正文命中、无结果三种情况。
This commit is contained in:
@@ -601,6 +601,7 @@ async def list_tickets(
|
|||||||
session: SessionDep,
|
session: SessionDep,
|
||||||
role: Annotated[str, Query(description="assigned|requested|all")] = "assigned",
|
role: Annotated[str, Query(description="assigned|requested|all")] = "assigned",
|
||||||
status: Annotated[list[str] | None, Query(description="按状态过滤,可重复")] = None,
|
status: Annotated[list[str] | None, Query(description="按状态过滤,可重复")] = None,
|
||||||
|
q: Annotated[str | None, Query(description="搜标题或正文")] = None,
|
||||||
limit: Annotated[int, Query(ge=1, le=100)] = 20,
|
limit: Annotated[int, Query(ge=1, le=100)] = 20,
|
||||||
cursor: Annotated[str | None, Query()] = None,
|
cursor: Annotated[str | None, Query()] = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
@@ -622,6 +623,11 @@ async def list_tickets(
|
|||||||
if status:
|
if status:
|
||||||
stmt = stmt.where(Item.status.in_(status))
|
stmt = stmt.where(Item.status.in_(status))
|
||||||
|
|
||||||
|
if q and q.strip():
|
||||||
|
# 与知识列表同一套模糊匹配:手机上找一张单,标题记不清时正文里的关键词也能命中
|
||||||
|
like = f"%{q.strip()}%"
|
||||||
|
stmt = stmt.where(or_(Item.title.ilike(like), Item.body_md.ilike(like)))
|
||||||
|
|
||||||
stmt = stmt.order_by(Item.updated_at.desc(), Item.id.desc()).limit(limit + 1)
|
stmt = stmt.order_by(Item.updated_at.desc(), Item.id.desc()).limit(limit + 1)
|
||||||
if parsed_cursor is not None:
|
if parsed_cursor is not None:
|
||||||
stmt = stmt.where(
|
stmt = stmt.where(
|
||||||
|
|||||||
@@ -315,6 +315,64 @@ async def test_ticket_list_scopes(client: AsyncClient) -> None:
|
|||||||
assert empty["tickets"] == []
|
assert empty["tickets"] == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_ticket_list_search(client: AsyncClient) -> None:
|
||||||
|
"""手机端工单列表的搜索框:标题和正文都能命中,不相关的不出现。"""
|
||||||
|
owner = await login(client)
|
||||||
|
ws = await create_workspace(client, owner, "工单搜索")
|
||||||
|
helper = await _member(client, owner, ws)
|
||||||
|
task = await _task(client, owner, ws["id"], "支付回调重复")
|
||||||
|
unwrap(
|
||||||
|
await client.patch(
|
||||||
|
f"{V1}/workspaces/{ws['id']}/items/{task['id']}",
|
||||||
|
json={"body_md": "网关在 23:00 之后重复回调了三次", "version": task["version"]},
|
||||||
|
headers=owner["headers"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
unwrap(
|
||||||
|
await client.post(
|
||||||
|
f"{V1}/workspaces/{ws['id']}/items/{task['id']}/promote",
|
||||||
|
json={"assignee_id": helper["user_id"]},
|
||||||
|
headers=owner["headers"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
other = await _task(client, owner, ws["id"], "登录页白屏")
|
||||||
|
unwrap(
|
||||||
|
await client.post(
|
||||||
|
f"{V1}/workspaces/{ws['id']}/items/{other['id']}/promote",
|
||||||
|
json={"assignee_id": helper["user_id"]},
|
||||||
|
headers=owner["headers"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
by_title = unwrap(
|
||||||
|
await client.get(
|
||||||
|
f"{V1}/workspaces/{ws['id']}/tickets",
|
||||||
|
params={"role": "all", "q": "支付"},
|
||||||
|
headers=helper["headers"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert [t["title"] for t in by_title["tickets"]] == ["支付回调重复"]
|
||||||
|
|
||||||
|
by_body = unwrap(
|
||||||
|
await client.get(
|
||||||
|
f"{V1}/workspaces/{ws['id']}/tickets",
|
||||||
|
params={"role": "all", "q": "网关"},
|
||||||
|
headers=helper["headers"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert [t["title"] for t in by_body["tickets"]] == ["支付回调重复"]
|
||||||
|
|
||||||
|
miss = unwrap(
|
||||||
|
await client.get(
|
||||||
|
f"{V1}/workspaces/{ws['id']}/tickets",
|
||||||
|
params={"role": "all", "q": "不存在的关键词"},
|
||||||
|
headers=helper["headers"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert miss["tickets"] == []
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_cross_tenant_ticket_invisible(client: AsyncClient) -> None:
|
async def test_cross_tenant_ticket_invisible(client: AsyncClient) -> None:
|
||||||
alice = await login(client)
|
alice = await login(client)
|
||||||
|
|||||||
Reference in New Issue
Block a user