T11.6: retire the per-browser Usage report (CR-019)
Removes the old "Usage logs" card from admin.html/admin.js (the
usage-admin panel + loadUsage(), D5/T7.10) now that T11.5's real,
server-side Activity & usage card exists and reads real per-user data
instead of per-browser localStorage.
wp-usage.js is deleted outright, along with its three <script> includes
(admin.html, wp-creation-index.html, work-package-suite.html) - it had
no reader left once the panel above it was removed (the "download the
full event log" button lived only in that panel), and per the original
CR-019 decision record it was never reliably tied to a real identity,
so it was never a candidate data source for the new report either.
Its two call sites (wp-creation-app.js, work-package-suite-app.js) keep
a local track() function as a documented no-op rather than having each
of their ~45 individual track('event', ...) call sites deleted one at a
time - that would be a much larger, riskier diff for the same outcome
(no data is recorded either way), and each call site still marks what
was worth recording if usage analytics are ever rebuilt server-side.
work-package-suite-app.js's dwell-timer plumbing (_stepEnter /
trackStepDwell), which only ever fed track(), was left in place for the
same reason: inert, not broken.
Also removes tests/usage_check.py, which tested exactly the retired
feature, and updates its line in docs/reference/file-map.md to point at
the 2026-09-17 decision record instead.
Verified:
- grep across the whole repo for WPUsage / wp-usage.js / usage-admin /
usage_check: no live references remain, only explanatory comments
and planning docs (decisions-2026-09-17.md, wave-11.md) that
describe the removal itself
- node --check on all three touched .js files: no syntax errors
- full backend smoke test (27/27) and seed_demo.py still pass
- tests/baseline_shots.py --pages admin,creator,sop at 390px/1440px,
run locally: all three pages render with no new JS errors (the one
"beforeunload" log line on sop/creator at 1440px is pre-existing
harness noise from wp-autosave.js's unsaved-work guard, unrelated
to this change) and no horizontal overflow; refreshed baseline
screenshots committed alongside this change
This commit is contained in:
@@ -1,192 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Is there exactly one analytics implementation, reported from admin? — D5, T7.10.
|
||||
|
||||
Usage analytics existed twice (creator + wizard), five of the nine colliding
|
||||
globals `creator-frame.md` counted, and the wizard's copy had no caller. One
|
||||
core survives in wp-usage.js; the pages keep thin track() wrappers; the report
|
||||
and its downloads live on the admin console behind the same role gate as the
|
||||
rest of that page. Keys are unchanged, so pre-move data still reads.
|
||||
|
||||
Self-contained: throwaway SQLite, its own uvicorn, headless Edge or Chrome.
|
||||
Exit 0 all passed, 1 a failure, 2 could not run.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
import cdp # noqa: E402
|
||||
from browser_check import seed, start_server, chk, _PASS, _FAIL # noqa: E402
|
||||
from sections_check import set_sop # noqa: E402
|
||||
from stepper_check import dismiss_dialogs # noqa: E402
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
HTML = os.path.join(ROOT, "html")
|
||||
|
||||
|
||||
def ascii_(v, n=280):
|
||||
return re.sub(r"\s+", " ", str(v)).encode("ascii", "replace").decode()[:n]
|
||||
|
||||
|
||||
def settle(seconds=0.6):
|
||||
time.sleep(seconds)
|
||||
|
||||
|
||||
def strip_js(src):
|
||||
src = re.sub(r"/\*.*?\*/", "", src, flags=re.S)
|
||||
return "\n".join(re.sub(r"(?<![:'\"])//.*$", "", ln) for ln in src.split("\n"))
|
||||
|
||||
|
||||
def main():
|
||||
exe = cdp.find_browser()
|
||||
if not exe:
|
||||
print("no headless-capable browser found; set WP_BROWSER.")
|
||||
return 2
|
||||
|
||||
# ── 1. the grep half: one implementation, no leftover controls ───────────
|
||||
print("\n1. grep: one implementation, nothing unreferenced")
|
||||
files = {}
|
||||
for name in os.listdir(HTML):
|
||||
if name.endswith((".js", ".html")):
|
||||
files[name] = strip_js(open(os.path.join(HTML, name), encoding="utf-8").read())
|
||||
|
||||
core_defs = [n for n, src in files.items() if "window.WPUsage" in src]
|
||||
chk("the WPUsage core is defined in wp-usage.js and only there",
|
||||
core_defs == ["wp-usage.js"], ascii_(core_defs))
|
||||
chk("the pages record THROUGH it - no page touches the storage keys directly",
|
||||
all("wp_iwp_analytics_v1" not in src and "wp_suite_analytics_v1" not in src
|
||||
for n, src in files.items()
|
||||
if n not in ("wp-usage.js",) and n.endswith(".js")))
|
||||
leftovers = {n: re.findall(r"analyticsLoad|analyticsSave|downloadAnalytics|showAnalytics"
|
||||
r"|ANALYTICS_KEY|USAGE_KEY|usageLoad|downloadUsage", src)
|
||||
for n, src in files.items() if n != "wp-usage.js"}
|
||||
leftovers = {n: v for n, v in leftovers.items() if v}
|
||||
chk("none of the five colliding globals survives anywhere; grep confirms",
|
||||
not leftovers, ascii_(leftovers))
|
||||
chk("no analytics control remains on the creator or the wizard; grep confirms",
|
||||
"Usage data" not in files["wp-creation-index.html"]
|
||||
and "showAnalytics" not in files["work-package-suite.html"])
|
||||
chk("both storage keys survive, verbatim, in the core (data continuity)",
|
||||
"wp_iwp_analytics_v1" in files["wp-usage.js"]
|
||||
and "wp_suite_analytics_v1" in files["wp-usage.js"])
|
||||
|
||||
tmpdir = tempfile.mkdtemp(prefix="wpsuite-usage-")
|
||||
db_path = os.path.join(tmpdir, "check.db")
|
||||
server = None
|
||||
browser = None
|
||||
try:
|
||||
tok = seed(db_path)
|
||||
set_sop(db_path, {})
|
||||
port = cdp.free_port()
|
||||
base = "http://127.0.0.1:%d" % port
|
||||
server = start_server(port, db_path)
|
||||
|
||||
browser = cdp.Browser(exe)
|
||||
page = browser.page()
|
||||
page.clear_cookies()
|
||||
page.set_cookie("wp_session", tok["root"])
|
||||
page.viewport(1440, 900)
|
||||
|
||||
# ── 2. recording still works from both tools ─────────────────────────
|
||||
print("\n2. the tools still record")
|
||||
page.goto(base + "/wp-creation-index.html?project=projA")
|
||||
dismiss_dialogs(page)
|
||||
settle(2.0)
|
||||
# Plant a LEGACY-format event under the pre-move key: the done-when is
|
||||
# that data recorded before this task is still readable after it.
|
||||
page.eval("""(() => {
|
||||
const d = JSON.parse(localStorage.getItem('wp_iwp_analytics_v1')) || {events: []};
|
||||
d.events.unshift({ts: '2026-07-01T10:00:00Z', session: 's_legacy',
|
||||
event: 'legacy_probe_event', detail: null});
|
||||
localStorage.setItem('wp_iwp_analytics_v1', JSON.stringify(d));
|
||||
})()""")
|
||||
n0 = page.eval("WPUsage.load(WPUsage.KEYS.creator).events.length")
|
||||
page.eval("track('probe_event')")
|
||||
chk("the creator's track() still lands events under the old key",
|
||||
page.eval("WPUsage.load(WPUsage.KEYS.creator).events.length") == n0 + 1)
|
||||
|
||||
page.goto(base + "/work-package-suite.html?tab=sop")
|
||||
dismiss_dialogs(page)
|
||||
settle(2.0)
|
||||
n0 = page.eval("WPUsage.load(WPUsage.KEYS.wizard).events.length")
|
||||
page.eval("track('probe_event_wizard')")
|
||||
chk("the wizard's track() still lands events under its old key",
|
||||
page.eval("WPUsage.load(WPUsage.KEYS.wizard).events.length") == n0 + 1)
|
||||
wiz_errors = [e for e in page.js_errors() if "beforeunload" not in e]
|
||||
chk("...and the wizard page throws no errors without its old globals "
|
||||
"(the blocked-beforeunload console line is BL-020, filtered not hidden)",
|
||||
not wiz_errors, ascii_(wiz_errors[:2]))
|
||||
|
||||
# ── 3. the report, on admin, behind the admin gate ───────────────────
|
||||
print("\n3. the admin report")
|
||||
page.goto(base + "/admin.html")
|
||||
dismiss_dialogs(page)
|
||||
settle(2.0)
|
||||
page.eval("loadUsage()")
|
||||
settle(0.5)
|
||||
report = page.eval("(document.getElementById('usage-admin')||{textContent:''}).textContent")
|
||||
chk("usage data is reachable from admin.html, both tools reported",
|
||||
"Work package creator" in report and "SOP wizard" in report, ascii_(report, 160))
|
||||
chk("the pre-move legacy event is readable in the report",
|
||||
"legacy_probe_event" in report)
|
||||
chk("this session's fresh events are in it too",
|
||||
"probe_event" in report and "probe_event_wizard" in report)
|
||||
chk("each tool offers its download from the report",
|
||||
page.eval("[...document.querySelectorAll('#usage-admin button')].length") >= 2)
|
||||
|
||||
page.viewport(390, 844, mobile=True)
|
||||
settle(0.6)
|
||||
fits = page.eval("""(() => {
|
||||
const box = document.getElementById('usage-admin');
|
||||
return box && box.scrollWidth <= box.clientWidth + 2
|
||||
&& document.documentElement.scrollWidth <= 392;
|
||||
})()""")
|
||||
chk("the report is usable at 390px - no sideways scrolling", bool(fits))
|
||||
page.viewport(1440, 900)
|
||||
settle(0.4)
|
||||
|
||||
# The same role gate as the rest of the console: a non-admin sees the
|
||||
# denied card and no cards, this one included.
|
||||
page.clear_cookies()
|
||||
page.set_cookie("wp_session", tok["pat"])
|
||||
page.goto(base + "/admin.html")
|
||||
dismiss_dialogs(page)
|
||||
settle(2.0)
|
||||
chk("a non-administrator gets the denied notice, not the usage report",
|
||||
page.eval("""(() => {
|
||||
const denied = document.getElementById('admin-denied');
|
||||
const usage = document.getElementById('usage-admin');
|
||||
const visible = el => !!el && el.offsetParent !== null;
|
||||
return visible(denied) && !visible(usage);
|
||||
})()"""))
|
||||
|
||||
js_errors = [e for e in page.js_errors() if "beforeunload" not in e]
|
||||
chk("no JavaScript errors anywhere in this run", not js_errors,
|
||||
ascii_(js_errors[:2]))
|
||||
|
||||
finally:
|
||||
if browser is not None:
|
||||
try:
|
||||
browser.close()
|
||||
except Exception:
|
||||
pass
|
||||
if server is not None:
|
||||
try:
|
||||
server.terminate()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print("\n" + "-" * 54)
|
||||
print("%d/%d checks passed." % (len(_PASS), len(_PASS) + len(_FAIL)))
|
||||
for f in _FAIL:
|
||||
print(" - " + f)
|
||||
return 1 if _FAIL else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user