fix(feed): a filter on a deleted tag matches nothing and its chip says so
A saved view or the stored filter outlives its tag. feedquery dropped the unknown id, so the view showed the whole feed under its old name. The id now lands in a MISSING_TAG_CATEGORY that filters to nothing in the SQL and in feed_summary's mirror alike; the feed chip reads "Deleted tag" (a placeholder while the tag list loads, the id if it failed). Channels' persisted tag filter gets the same chip, first in the row so the +N overflow cannot fold it away.
This commit is contained in:
@@ -106,15 +106,26 @@ def _tag_visible(user: User):
|
||||
return or_(ChannelTag.user_id.is_(None), ChannelTag.user_id == user.id)
|
||||
|
||||
|
||||
# The pseudo-category of a selected tag id no Tag row answers (a saved view or the feed state outlives
|
||||
# a deleted tag). It filters like a category whose tags no channel carries — to nothing, in the SQL and
|
||||
# in feed_summary's mirror alike — instead of the id being dropped, which showed the WHOLE feed under
|
||||
# the view's old name (E32 S2). No real category is called this (language/topic/other).
|
||||
MISSING_TAG_CATEGORY = "missing"
|
||||
|
||||
|
||||
def tags_by_category(db: Session, tag_ids: list[int]) -> dict[str, list[int]]:
|
||||
"""Group tag ids by their category (language/topic/other). The single source for the category split
|
||||
shared by the feed's tag WHERE (filtered_query) and the facet/count summary (feed_summary), so the
|
||||
two can't disagree on which selected tag belongs to which category."""
|
||||
two can't disagree on which selected tag belongs to which category. An unknown id lands in
|
||||
MISSING_TAG_CATEGORY."""
|
||||
if not tag_ids:
|
||||
return {} # skip the pointless `WHERE id IN ()` round-trip on the no-tag-filter hot path
|
||||
out: dict[str, list[int]] = {}
|
||||
for tid, cat in db.execute(select(Tag.id, Tag.category).where(Tag.id.in_(tag_ids))).all():
|
||||
out.setdefault(cat, []).append(tid)
|
||||
known = {tid for ids in out.values() for tid in ids}
|
||||
if missing := [tid for tid in tag_ids if tid not in known]:
|
||||
out[MISSING_TAG_CATEGORY] = missing
|
||||
return out
|
||||
|
||||
|
||||
|
||||
@@ -168,9 +168,30 @@ _SUMMARY_CASES = [
|
||||
"show=all&untagged=true&tags={L1}",
|
||||
"show=all&q=clip",
|
||||
"show=unwatched",
|
||||
# A deleted tag's id (a saved view or the feed state still holds it) matches nothing.
|
||||
"show=all&tags=987654",
|
||||
"show=all&tags={L1}&tags=987654",
|
||||
]
|
||||
|
||||
|
||||
def test_a_deleted_tags_id_filters_to_nothing_not_to_everything(db, as_user, make_user):
|
||||
"""A saved view or the feed's filter state keeps a tag id after the tag is deleted. The feed used
|
||||
to drop an unknown id, so that view silently showed the WHOLE feed under its old name (E32 S2).
|
||||
A filter on a tag that no longer exists matches nothing; the chip says the tag is gone."""
|
||||
user, tag_ids = _seed_tagged(db, make_user)
|
||||
client = as_user(user)
|
||||
everything = client.get("/api/feed/count?show=all")
|
||||
assert everything.status_code == 200
|
||||
assert everything.json()["count"] == 8
|
||||
for qs in ("show=all&tags=987654", f"show=all&tags={tag_ids['L1']}&tags=987654"):
|
||||
r = client.get(f"/api/feed/count?{qs}")
|
||||
assert r.status_code == 200
|
||||
assert r.json()["count"] == 0, qs
|
||||
r = client.get(f"/api/feed?{qs}")
|
||||
assert r.status_code == 200
|
||||
assert r.json()["items"] == [], qs
|
||||
|
||||
|
||||
def test_untagged_filter_keeps_only_channels_the_user_has_not_tagged(db, as_user, make_user):
|
||||
"""The "No tag" filter (item B): `untagged=true` keeps only channels with NO tag the USER personally
|
||||
attached (no user-owned ChannelTag). System/auto tags (user_id NULL) don't count, and another user's
|
||||
|
||||
Reference in New Issue
Block a user