forked from Mike/freno-dev
downloads: track every door and surface them on /analytics
The analytics page could not answer "how many downloads, of which
product". Three of the four download doors recorded nothing: the tRPC
click proxy only left the generic request row, whose path is the raw URL
with the asset buried in `?batch=1&input={"asset_name":...}`, and the S3
file route that serves Sparkle updates and the Nook app logged nothing
at all. The one door that did log (the Nook redirect) kept a private copy
of the logVisit/enrich/flush sequence, under its own path.
All doors now go through trackDownload, which writes one row per event
under /api/downloads/<asset> and flushes it inside the request — a click
is low-volume and serverless is not guaranteed to run the 30s batch
timer after the response. The file route maps its prefix allowlist to
asset keys, so a version bump stays one asset rather than one per
release. getAnalyticsSummary groups those rows into a downloads block and
the freno.me panel renders it under the overview cards.
History is recovered to the degree it exists: the Nook door's rows and
the tRPC clicks still carry enough to derive from (asset name in the URL).
scripts/backfill-downloads.ts inserts those derived rows with a
`backfill-<source id>` primary key and INSERT OR IGNORE, leaving the
originals untouched, so re-running is a no-op. Run against the live table
it added 66 rows: gaze 42, thenook 19, inputhalo 4, lineage 1.
Tests: new backfill suite (derivation, visitor detail carried over,
idempotency, empty history) and summariseDownloads cases; the Nook route
test now pins the canonical path, and the NOOK-337 allowlist audit reads
the map keys instead of quoted strings. Red-checked by stubbing the
downloads block back to zeros — the summary case fails, then passes.
Full suite matches master.
Co-Authored-By: nw-flash <noreply@nw-flash.local>
MISC-48
This commit is contained in:
@@ -202,6 +202,32 @@ function FrenomePanel(props: FrenomePanelProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Downloads — every door logs one row at /api/downloads/<asset> */}
|
||||
<Show when={data.downloads.total > 0}>
|
||||
<div class="bg-surface0 border-surface1 mb-8 rounded-lg border shadow">
|
||||
<div class="border-surface1 border-b p-6">
|
||||
<h2 class="text-text text-2xl font-bold">Downloads</h2>
|
||||
<p class="text-subtext0 mt-1 text-sm">
|
||||
{formatNumber(data.downloads.total)} events in the last
|
||||
{props.days === 1 ? " 24 hours" : ` ${props.days} days`} —
|
||||
landing page clicks and files served, by product
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-3 p-6">
|
||||
<For each={data.downloads.byAsset}>
|
||||
{(download) => (
|
||||
<StatBar
|
||||
label={download.asset}
|
||||
count={download.count}
|
||||
total={data.downloads.total}
|
||||
color="bg-teal-600"
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Performance */}
|
||||
<Show when={performanceStats()} keyed>
|
||||
{(ps) => (
|
||||
|
||||
Reference in New Issue
Block a user