Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion crates/keryx-render/assets/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@
function matchesTags(row) {
var tags = rowTags(row);
if (untagged) return tags.length === 0;
return !selectedTags.size || tags.some(function (tag) { return selectedTags.has(tag.id); });
return Array.from(selectedTags).every(function (id) {
return tags.some(function (tag) { return tag.id === id; });
});
}
function renderTagFilters(query) {
if (!managementEnabled) return;
Expand Down
2 changes: 1 addition & 1 deletion crates/keryx-render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ pub fn render_dashboard(
</body>
</html>"#,
tag_filter = if management_enabled {
r#"<details id="tag-filter" class="tag-filter"><summary id="tag-filter-summary">Tags: all</summary><div class="tag-filter-menu"><label for="tag-filter-search">Find tags</label><input id="tag-filter-search" type="search"><div id="tag-filter-options"></div><label><input id="tag-untagged" type="checkbox"> Untagged only <span id="untagged-count"></span></label><p>Match any selected tag</p><button class="button" type="button" data-clear-tags>Clear all</button> <button class="button" id="tag-filter-done" type="button">Done</button></div></details>"#
r#"<details id="tag-filter" class="tag-filter"><summary id="tag-filter-summary">Tags: all</summary><div class="tag-filter-menu"><label for="tag-filter-search">Find tags</label><input id="tag-filter-search" type="search"><div id="tag-filter-options"></div><label><input id="tag-untagged" type="checkbox"> Untagged only <span id="untagged-count"></span></label><p>Match all selected tags</p><button class="button" type="button" data-clear-tags>Clear all</button> <button class="button" id="tag-filter-done" type="button">Done</button></div></details>"#
} else {
""
},
Expand Down
28 changes: 21 additions & 7 deletions tests/dashboard/tags.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,34 @@ test.beforeEach(async ({ request }) => {
for (const draft of body.drafts) await request.delete(`/api/drafts/${draft.draftId}?purge=true`);
});

test('ANY filters combine with repository, search and availability; URL and snapshots retain state', async ({ page, request }) => {
const { a, b, c, planning, review } = await seed(request);
await controlledRefresh(page);
await page.goto(`/?tag=${planning.id}&tag=${review.id}&draft=${a}&sort=tag`);
test('selecting a second tag narrows results to drafts with both tags', async ({ page, request }) => {
const { a, b, planning, review } = await seed(request);
await page.goto('/');
await page.locator('#tag-filter-summary').click();
await page.locator(`[data-tag-choice="${planning.id}"]`).check();
await expect(visibleRows(page)).toHaveCount(2);
await page.locator(`[data-tag-choice="${review.id}"]`).check();
await expect(visibleRows(page)).toHaveCount(1);
await expect(visibleRows(page)).toHaveAttribute('data-draft-id', b);
await expect(page.locator(`.draft-row[data-draft-id="${a}"]`)).toBeHidden();
await page.locator(`[data-tag-choice="${review.id}"]`).uncheck();
await expect(visibleRows(page)).toHaveCount(2);
});

test('tag filters combine with repository, search and availability; URL and snapshots retain state', async ({ page, request }) => {
const { b, c, planning, review } = await seed(request);
await controlledRefresh(page);
await page.goto(`/?tag=${planning.id}&tag=${review.id}&draft=${b}&sort=tag`);
await expect(visibleRows(page)).toHaveCount(1);
await expect(page.locator('#tag-selections .tag-chip')).toHaveCount(3);
await page.locator('#repo-filter').selectOption('test/one');
await page.locator('#repo-filter').selectOption('test/two');
await expect(visibleRows(page)).toHaveCount(1);
await page.locator('#draft-search').fill('planning');
await expect(visibleRows(page)).toHaveCount(1);
await refresh(page);
await expect(page.locator('#draft-search')).toHaveValue('planning');
await expect(page.locator('#repo-filter')).toHaveValue('test/one');
await expect(page.locator('#detail-id')).toHaveText(a);
await expect(page.locator('#repo-filter')).toHaveValue('test/two');
await expect(page.locator('#detail-id')).toHaveText(b);
await page.reload();
await expect(visibleRows(page)).toHaveCount(1);
await page.locator('#tag-filter-summary').click();
Expand Down
2 changes: 1 addition & 1 deletion website/src/content/docs/docs/tagging.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Names accept ASCII letters, digits, spaces and hyphens, with a maximum of **32 c

Open **Tags** in the toolbar and tick one or more tags. The list filters immediately while the dropdown stays open. Search within the dropdown to find a tag; longer lists scroll.

- Multiple selected tags match **any** of them. Selecting `planning` and `needs-review` shows drafts carrying either label, including drafts with both.
- Multiple selected tags match **all** of them. Selecting `planning` and `needs-review` shows drafts carrying both labels.
- Repository, availability and text search narrow that result further. Text search also matches tag names.
- **Untagged only** shows drafts with no tags and clears selected tag filters. Selecting a tag leaves Untagged mode.
- Remove a selected filter chip to clear that filter, or choose **Clear all** to clear the tag filters.
Expand Down
Loading