fix(review): slug-scoped tags, decode names, drop fig picker & AI labels
- Taxonomy sync keeps fig-/feat- by slug (hyphen), not name; HTML-unescape names - Review: remove AI assist labels; Features picker for feat-*; derive fig_tag_ids from Figure term names (casefold match, no bridges); decodeName on display - Unit tests for scoped_tags, fig match, Review UI contract; docs slug wording
This commit is contained in:
+25
-7
@@ -1,11 +1,14 @@
|
||||
"""Taxonomy sync: pull Woo categories/tags/attributes/terms into the local cache.
|
||||
|
||||
Rebuild happens in one transaction; on any failure the prior cache is kept.
|
||||
Only fig-* / feat-* tag families are retained (METADATA-15). taxonomy_anchor is a
|
||||
stable hash of the stored subset, recorded only when it changes.
|
||||
Only fig-* / feat-* tag families are retained by **slug** prefix with hyphen
|
||||
(SETTINGS-51 / METADATA-15). taxonomy_anchor is a stable hash of the stored
|
||||
subset, recorded only when it changes. Display names are HTML-unescaped so
|
||||
Woo entities like ``&`` store as ``&``.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import html
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
|
||||
@@ -16,8 +19,23 @@ def _now_iso() -> str:
|
||||
return datetime.now(timezone.utc).replace(microsecond=0, tzinfo=None).isoformat()
|
||||
|
||||
|
||||
def _decode_name(name: str | None) -> str:
|
||||
"""Decode HTML entities Woo puts in taxonomy display names."""
|
||||
return html.unescape(name or "")
|
||||
|
||||
|
||||
def _scoped_tags(tags: list[dict]) -> list[dict]:
|
||||
return [t for t in tags if t.get("name", "").startswith(("fig-", "feat-"))]
|
||||
"""Keep product tags whose slug starts with fig- or feat- (hyphen required).
|
||||
|
||||
Live Woo uses human display names (e.g. "Curl") with slugs like fig-curl.
|
||||
Filtering by name prefix kept 0 live tags; slug is authoritative.
|
||||
"""
|
||||
out = []
|
||||
for t in tags:
|
||||
slug = t.get("slug") or ""
|
||||
if slug.startswith("fig-") or slug.startswith("feat-"):
|
||||
out.append(t)
|
||||
return out
|
||||
|
||||
|
||||
def _normalize_category(c: dict) -> tuple:
|
||||
@@ -34,7 +52,7 @@ def _normalize_category(c: dict) -> tuple:
|
||||
woo_id = c.get("woo_id", c.get("id"))
|
||||
if woo_id is not None:
|
||||
woo_id = int(woo_id)
|
||||
return (c["id"], c["name"], c["slug"], parent_id, woo_id)
|
||||
return (c["id"], _decode_name(c["name"]), c["slug"], parent_id, woo_id)
|
||||
|
||||
|
||||
def _subset_hash(categories: list[dict], attributes: list[dict],
|
||||
@@ -90,16 +108,16 @@ def sync(client: WooClient, conn) -> dict:
|
||||
|
||||
conn.executemany(
|
||||
"INSERT INTO woo_attributes(id, name, slug, woo_id) VALUES (?,?,?,?)",
|
||||
[(a["id"], a["name"], a["slug"], a.get("woo_id")) for a in attributes],
|
||||
[(a["id"], _decode_name(a["name"]), a["slug"], a.get("woo_id")) for a in attributes],
|
||||
)
|
||||
for attr_id, terms in terms_by_attr.items():
|
||||
conn.executemany(
|
||||
"INSERT INTO woo_attribute_terms(id, attribute_id, name, slug, woo_id) VALUES (?,?,?,?,?)",
|
||||
[(t["id"], attr_id, t["name"], t["slug"], t.get("woo_id")) for t in terms],
|
||||
[(t["id"], attr_id, _decode_name(t["name"]), t["slug"], t.get("woo_id")) for t in terms],
|
||||
)
|
||||
conn.executemany(
|
||||
"INSERT INTO woo_tags(id, name, slug, woo_id) VALUES (?,?,?,?)",
|
||||
[(t["id"], t["name"], t["slug"], t.get("woo_id")) for t in scoped_tags],
|
||||
[(t["id"], _decode_name(t["name"]), t["slug"], t.get("woo_id")) for t in scoped_tags],
|
||||
)
|
||||
|
||||
anchor = _subset_hash(categories, attributes, terms_by_attr, scoped_tags)
|
||||
|
||||
@@ -95,12 +95,29 @@ def test_401_raises_auth_error(db_dir):
|
||||
c.get_categories()
|
||||
|
||||
|
||||
def test_scoped_tags_by_slug_not_name():
|
||||
"""SETTINGS-51: keep fig-/feat- by slug prefix with hyphen; human names stay."""
|
||||
tags = [
|
||||
{"id": 1, "name": "Curl", "slug": "fig-curl"},
|
||||
{"id": 2, "name": "Knot", "slug": "feat-knot"},
|
||||
{"id": 3, "name": "fig-looks-like", "slug": "other"}, # name prefix alone — drop
|
||||
{"id": 4, "name": "Figured", "slug": "figured"}, # no hyphen — drop
|
||||
{"id": 5, "name": "other", "slug": "other-tag"},
|
||||
]
|
||||
kept = taxonomy_mod._scoped_tags(tags)
|
||||
assert {t["id"] for t in kept} == {1, 2}
|
||||
|
||||
|
||||
def test_sync_populates_scoped(db_dir):
|
||||
cats = (200, {"X-WP-TotalPages": "1"}, json.dumps([{"id": 1, "name": "Cedar", "slug": "cedar", "parent_id": None, "woo_id": 10}]).encode())
|
||||
# Live Woo: human names + fig-/feat- slugs. Name-prefix filter would keep 0.
|
||||
cats = (200, {"X-WP-TotalPages": "1"}, json.dumps([
|
||||
{"id": 1, "name": "Cookies & Burl Slices", "slug": "cookies-burl", "parent_id": None, "woo_id": 10},
|
||||
]).encode())
|
||||
tags = (200, {"X-WP-TotalPages": "1"}, json.dumps([
|
||||
{"id": 2, "name": "fig-curl", "slug": "fig-curl", "woo_id": 20},
|
||||
{"id": 3, "name": "feat-knot", "slug": "feat-knot", "woo_id": 21},
|
||||
{"id": 4, "name": "other-tag", "slug": "other-tag", "woo_id": 22}, # must be dropped
|
||||
{"id": 2, "name": "Curl", "slug": "fig-curl", "woo_id": 20},
|
||||
{"id": 3, "name": "Knot", "slug": "feat-knot", "woo_id": 21},
|
||||
{"id": 4, "name": "fig-fake", "slug": "other-tag", "woo_id": 22}, # name looks scoped; slug not — drop
|
||||
{"id": 5, "name": "other-tag", "slug": "other-tag-2", "woo_id": 23},
|
||||
]).encode())
|
||||
attrs = (200, {"X-WP-TotalPages": "1"}, json.dumps([{"id": 5, "name": "Edge Type", "slug": "edge", "woo_id": 30}]).encode())
|
||||
terms = (200, {"X-WP-TotalPages": "1"}, json.dumps([{"id": 6, "name": "Flat", "slug": "flat", "woo_id": 40}]).encode())
|
||||
@@ -108,8 +125,11 @@ def test_sync_populates_scoped(db_dir):
|
||||
result = taxonomy_mod.sync(c, db_mod.get_connection())
|
||||
conn = db_mod.get_connection()
|
||||
assert [r[0] for r in conn.execute("SELECT id FROM woo_categories")] == [1]
|
||||
tag_ids = {r[0] for r in conn.execute("SELECT id FROM woo_tags")}
|
||||
assert tag_ids == {2, 3} # fig- + feat- kept, other-tag dropped
|
||||
assert conn.execute("SELECT name FROM woo_categories WHERE id=1").fetchone()[0] == "Cookies & Burl Slices"
|
||||
tag_rows = {r[0]: (r[1], r[2]) for r in conn.execute("SELECT id, name, slug FROM woo_tags")}
|
||||
assert set(tag_rows) == {2, 3} # fig- + feat- by slug; name-only fig-fake dropped
|
||||
assert tag_rows[2] == ("Curl", "fig-curl")
|
||||
assert tag_rows[3] == ("Knot", "feat-knot")
|
||||
assert [r[0] for r in conn.execute("SELECT id FROM woo_attribute_terms")] == [6]
|
||||
assert result["taxonomy_anchor"]
|
||||
assert conn.execute("SELECT value FROM settings WHERE key='taxonomy_last_sync_at'").fetchone()[0]
|
||||
|
||||
Reference in New Issue
Block a user