fix(plausible): register a team so v3 ingests events; widen post-restore health wait
continuous-integration/drone/push Build is failing

plausible v3 (community-edition) only ingests events for a site that belongs to
a TEAM. The custom tier registered a site row and nothing else, which was enough
for v2 — under v3 the POST still acks 202 and the row still exists in postgres,
but every event is discarded. ClickHouse records the reason itself in
ingest_counters as dropped_not_found, and events_v2 stays empty, so it presents
as a silent ingestion stall.

Verified on cc-ci against v3.2.1: identical site row with no team ->
dropped_not_found and 0 rows; with a team linked -> buffered and the rows land.

_register_site now provisions a team and links the site, guarded on the schema
actually having teams so it stays a no-op on v2 (the upgrade tier deploys the
older base first).

Separately, the custom health check waited 60s for /api/health. That tier runs
after backup/restore, which disrupts postgres under the app and restarts it, and
v3 boots through sleep 10 + createdb + migrate + cache warmers before health
flips to 200. Widened to 300s, still far inside the recipe HTTP_TIMEOUT of 1200.
The assertion is unchanged: a hard 200 from the real readiness endpoint.

Neither change weakens a test - the event tests still require the row to arrive
in ClickHouse and match what was sent.
This commit is contained in:
cc-ci
2026-08-11 05:18:18 +00:00
parent 0a229ac016
commit eb1d6d9161
2 changed files with 42 additions and 4 deletions
+35 -3
View File
@@ -14,7 +14,10 @@ Both assert real app state (the event reached the analytics store), not just the
plausible only ingests events for *known* sites — the in-memory `sites_cache` gates ingestion and
drops events for unregistered domains (empirically confirmed: an event for an unregistered domain
never appears in events_v2). So each test first registers a site row in the metadata postgres, then
never appears in events_v2). From v3 (community-edition) a site is only "known" once it belongs to a
TEAM; a teamless site is dropped as `dropped_not_found` while the POST still acks 202, so the failure
looks like a silent ingestion stall. `_register_site` therefore provisions a team as well when the
schema has one. So each test first registers a site row in the metadata postgres, then
POSTs repeatedly while polling ClickHouse: the sites_cache must refresh to admit the new site and the
event write-buffer must flush to ClickHouse, so the first landing is not instantaneous. Re-POSTing the
same event is safe — we assert the row count is >= 1.
@@ -51,11 +54,40 @@ def _ch(domain: str, sql: str) -> str:
def _register_site(domain: str, site: str) -> None:
"""Insert a site row into the metadata postgres (`db` service) so plausible will ingest events for
it. Idempotent (ON CONFLICT DO NOTHING)."""
"""Register `site` in the metadata postgres so plausible will ingest events for it.
Idempotent. Works against BOTH schema generations, because the upgrade tier deploys an older
base version before upgrading:
* v2 (`plausible/analytics`) — a row in `sites` is sufficient.
* v3 (`ghcr.io/plausible/community-edition`) — sites belong to a TEAM, and ingestion drops
events for a site whose team is missing. The POST still acks 202 and the row still exists in
postgres, so the only visible symptom is that nothing ever reaches ClickHouse; the reason is
recorded in ClickHouse's own `ingest_counters` as `dropped_not_found`. Verified on cc-ci
against v3.2.1: identical site row, no team → `dropped_not_found`; with a team linked →
`buffered` and the row appears in `events_v2`.
The team block is guarded on the schema actually having teams, so this stays a no-op on v2
rather than branching on a version string.
"""
sql = (
"INSERT INTO sites (domain, timezone, inserted_at, updated_at, native_stats_start_at) "
f"VALUES ('{site}','UTC', now(), now(), now()) ON CONFLICT (domain) DO NOTHING; "
"DO $ccci$ "
"BEGIN "
" IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'teams') "
" AND EXISTS (SELECT 1 FROM information_schema.columns "
" WHERE table_name = 'sites' AND column_name = 'team_id') THEN "
" INSERT INTO teams (name, inserted_at, updated_at, accept_traffic_until, setup_complete) "
" SELECT 'cc-ci', now(), now(), now() + interval '365 days', true "
" WHERE NOT EXISTS (SELECT 1 FROM teams WHERE name = 'cc-ci'); "
" UPDATE sites "
" SET team_id = COALESCE(team_id, (SELECT id FROM teams WHERE name = 'cc-ci' LIMIT 1)), "
" accept_traffic_until = COALESCE(accept_traffic_until, now() + interval '365 days') "
f" WHERE domain = '{site}'; "
" END IF; "
"END "
"$ccci$; "
f"SELECT domain FROM sites WHERE domain = '{site}';"
)
out = lifecycle.exec_in_app(
+7 -1
View File
@@ -17,6 +17,12 @@ def test_plausible_root_serves(live_app):
62-char SECRET_KEY_BASE, see recipe_meta.EXTRA_ENV); the dedicated
/api/health endpoint is.
"""
# The custom tier runs AFTER the backup/restore tier, which disrupts postgres under the app and
# restarts it. v3 (community-edition) then boots through `sleep 10` + `db createdb` + `db migrate`
# + cache warmers before /api/health flips to 200, which does not fit in 60s — that is what put
# this recipe RED on build 1224 while install/upgrade/backup/restore all passed. The assertion is
# unchanged (still a hard 200 from the real readiness endpoint); only the wait matches the boot
# profile the recipe already declares via recipe_meta.HTTP_TIMEOUT (1200).
url = f"https://{live_app}/api/health"
status, _ = harness_http.retry_http_get(url, expect_status=(200,), max_wait=60, interval=3)
status, _ = harness_http.retry_http_get(url, expect_status=(200,), max_wait=300, interval=5)
assert status == 200, f"GET {url} HTTP {status}"