feat: filter Find Work by repository and label (Closes #681)
CI / lint (pull_request) Successful in 1m26s
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-12 21:33:27 +00:00
1 parent 057bec7dc2
commit f031a37e8d
7 files changed
+203 -10

No files matched your search

+41 -4
View File
@@ -269,7 +269,7 @@ const pages={{
}};
const controller=createFindWork({{
fetchJson:path=>path.includes('/claim') ? Promise.resolve({{repository:'stackchain/dashboard',number:671,assignees:['timmy']}}) :
Promise.resolve(pages[path.endsWith('=2') ? 2 : 1]),
Promise.resolve(pages[path.includes('page=2') ? 2 : 1]),
onItems:()=>{{}}, onPagination:()=>{{}}, onStatus:()=>{{}}, onSelection:()=>{{}},
}});
controller.load().then(()=>{{
@@ -426,9 +426,9 @@ second.then(()=>{{
assert run_node(script) == {
"calls": [
"api/v1/available-issues?page=1&q=api",
"api/v1/available-issues?page=1&q=dashboard",
"api/v1/available-issues?page=1&q=worker",
"api/v1/available-issues?page=1&facets=true&q=api",
"api/v1/available-issues?page=1&facets=true&q=dashboard",
"api/v1/available-issues?page=1&facets=true&q=worker",
],
"rendered": [[673], [674]],
"items": [674],
@@ -449,3 +449,40 @@ def test_mobile_find_work_search_has_clear_live_results_and_touch_targets():
assert "#find-work-search" in dashboard
assert ".find-work-search" in css
assert ".find-work-search button" in css and "min-height:44px" in css
def test_find_work_facets_serialize_filters_and_preserve_selection():
script = f"""
const createFindWork=require({json.dumps(str(PICK_WORK))});
const calls=[];
const controller=createFindWork({{
fetchJson:path=>{{ calls.push(path); return Promise.resolve({{items:[{{id:1,repository:'stackchain/api',number:1}}],page:1,total:1,has_more:false,facets:{{repositories:['stackchain/api'],labels:['critical']}}}}); }},
onItems:()=>{{}}, onPagination:()=>{{}}, onStatus:()=>{{}}, onSelection:()=>{{}}, onFacets:()=>{{}},
}});
controller.reset({{items:[{{id:9,repository:'stackchain/old',number:9}}],page:1,total:1,has_more:false}});
controller.startSelection(); controller.toggleSelection(controller.items()[0]);
controller.setFilters({{repositories:['stackchain/api'],labels:['critical']}}).then(()=>process.stdout.write(JSON.stringify({{
calls, filters:controller.filters(), selected:controller.selectedItems().map(item=>item.number)
}})));
"""
assert run_node(script) == {
"calls": ["api/v1/available-issues?page=1&facets=true&repository=stackchain%2Fapi&label=critical"],
"filters": {"repositories": ["stackchain/api"], "labels": ["critical"]},
"selected": [9],
}
def test_mobile_find_work_exposes_accessible_touch_sized_facets():
html = HTML.read_text()
dashboard = DASHBOARD.read_text()
css = CSS.read_text()
assert 'id="find-work-filters"' in html
assert 'id="find-work-repository-filters"' in html
assert 'id="find-work-label-filters"' in html
assert 'id="clear-find-work-filters"' in html
assert 'aria-label="Filter available work"' in html
assert "setFilters(next)" in PICK_WORK.read_text()
assert ".find-work-filters" in css
assert ".find-work-filter-option" in css and "min-height:44px" in css
+68
View File
@@ -248,6 +248,74 @@ async def test_available_issue_search_filters_full_catalog_before_pagination(mon
assert by_title.json() == by_repo.json()
@pytest.mark.anyio
async def test_available_issue_facets_filter_before_pagination_and_describe_catalog(monkeypatch):
async def available():
return [
{"repository": "stackchain/api", "number": 1, "title": "Critical API", "labels": ["critical", "backend"]},
{"repository": "stackchain/api", "number": 2, "title": "API docs", "labels": ["docs"]},
{"repository": "stackchain/dashboard", "number": 3, "title": "Critical UI", "labels": ["critical", "frontend"]},
]
monkeypatch.setattr(main.gitea_proxy, "available_issue_snapshot", available)
transport = httpx.ASGITransport(app=main.app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get(
"/api/v1/available-issues",
params=[("repository", "stackchain/api"), ("label", "critical")],
)
assert response.status_code == 200
assert response.json() == {
"items": [{"repository": "stackchain/api", "number": 1, "title": "Critical API", "labels": ["critical", "backend"]}],
"page": 1,
"total": 1,
"has_more": False,
"facets": {
"repositories": ["stackchain/api", "stackchain/dashboard"],
"labels": ["backend", "critical", "docs", "frontend"],
},
}
@pytest.mark.anyio
async def test_available_issue_facets_or_within_each_facet_and_compose_with_search(monkeypatch):
async def available():
return [
{"repository": "stackchain/api", "number": 1, "title": "Repair worker", "labels": ["backend"]},
{"repository": "stackchain/web", "number": 2, "title": "Repair mobile", "labels": ["frontend"]},
{"repository": "stackchain/docs", "number": 3, "title": "Repair guide", "labels": ["docs"]},
{"repository": "stackchain/api", "number": 4, "title": "Routine task", "labels": ["backend"]},
]
monkeypatch.setattr(main.gitea_proxy, "available_issue_snapshot", available)
transport = httpx.ASGITransport(app=main.app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get(
"/api/v1/available-issues",
params=[("q", "repair"), ("repository", "stackchain/api"), ("repository", "stackchain/web"),
("label", "backend"), ("label", "frontend")],
)
assert [item["number"] for item in response.json()["items"]] == [1, 2]
@pytest.mark.anyio
async def test_available_issue_facets_reject_excess_values_before_scanning(monkeypatch):
async def must_not_scan():
raise AssertionError("invalid facets must be rejected before catalog scan")
monkeypatch.setattr(main.gitea_proxy, "available_issue_snapshot", must_not_scan)
transport = httpx.ASGITransport(app=main.app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get(
"/api/v1/available-issues",
params=[("label", f"label-{index}") for index in range(11)],
)
assert response.status_code == 422
@pytest.mark.anyio
async def test_available_issue_search_rejects_oversized_query_without_scanning(monkeypatch):
async def must_not_scan():
+4 -1
View File
@@ -3345,7 +3345,10 @@ controller.load().then(() => controller.loadMore()).then(() =>
)
output = json.loads(result.stdout)
assert output["calls"] == ["api/v1/available-issues?page=1", "api/v1/available-issues?page=2"]
assert output["calls"] == [
"api/v1/available-issues?page=1&facets=true",
"api/v1/available-issues?page=2&facets=true",
]
assert [item["id"] for item in output["items"]] == [1, 2]
assert output["pages"][-1] == {"page": 2, "total": 2, "has_more": False}