Skip to content

Commit 043895f

Browse files
committed
fix(site): show full dataset history, not just the last 7 commits
The homepage History section called the GitHub commits API live (per_page=8, sliced to 7) and re-fetched the dump at each SHA, so older syncs were invisible and the unauthenticated rate limit (60/h) made it flaky. Precompute the whole timeline at deploy time instead: build-history.mjs walks the full git history of site/public/v1/index.json and writes site/public/v1/history.json (a build-only, gitignored artifact). The page reads that one static file and only falls back to the GitHub API for local `astro dev`. deploy-pages now checks out at fetch-depth: 0 so the generator can see old commits. Refs #1
1 parent a57d207 commit 043895f

6 files changed

Lines changed: 160 additions & 9 deletions

File tree

.github/workflows/deploy-pages.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ jobs:
2424
runs-on: ubuntu-latest
2525
steps:
2626
- uses: actions/checkout@v4
27+
with:
28+
# build-history.mjs walks the full git history of the public dump to
29+
# rebuild the homepage growth timeline; a shallow clone hides old syncs.
30+
fetch-depth: 0
2731

2832
- uses: actions/setup-node@v4
2933
with:

.gitignore

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ env/
3131
# Note: data/_staging/ (raw collected candidate pool) is intentionally tracked —
3232
# comprehensive data collection is a purpose of this repo.
3333

34-
# Verification layer caches: full Tier 0 scores + network caches are cheap to
35-
# recompute. Only data/_verify/ledger.jsonl (the promotion audit trail) is tracked.
36-
data/_verify/state/
34+
# Build-only: regenerated from full git history on every Pages deploy (site/scripts/build-history.mjs)
35+
site/public/v1/history.json
3736

3837
# Testing / coverage
3938
.pytest_cache/

site/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"private": true,
66
"scripts": {
77
"dev": "astro dev",
8+
"build:history": "node scripts/build-history.mjs",
9+
"prebuild": "node scripts/build-history.mjs",
810
"build": "astro build",
911
"preview": "astro preview"
1012
},

site/scripts/build-history.mjs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Precompute the dataset growth timeline from git history at build time.
2+
//
3+
// The homepage History section used to call the GitHub API live and could only
4+
// show the last ~7 commits (per_page=8 + slice(7)), so older syncs were invisible
5+
// and the unauthenticated API rate limit (60/h) made it fragile. Instead we walk
6+
// the full git history of `site/public/v1/index.json` once during the deploy build
7+
// and emit `site/public/v1/history.json` — a single static file the page reads.
8+
//
9+
// Build-only artifact (gitignored): regenerated on every Pages deploy, so it is
10+
// always complete and never churns data PRs. Requires a full-depth checkout
11+
// (fetch-depth: 0) to see old commits; degrades to an empty timeline otherwise.
12+
13+
import { execFileSync } from "node:child_process";
14+
import { mkdirSync, writeFileSync } from "node:fs";
15+
import { dirname, resolve } from "node:path";
16+
import { fileURLToPath } from "node:url";
17+
18+
const SITE_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
19+
const REPO_ROOT = resolve(SITE_DIR, "..");
20+
const TRACKED = "site/public/v1/index.json";
21+
const OUT = resolve(SITE_DIR, "public/v1/history.json");
22+
const REPO_URL = "https://github.com/GetTechAPI/TechAPI";
23+
24+
// Categories we sum into the record total (matches the public dump manifest).
25+
const ORDER = ["smartphones", "tablets", "watches", "pdas", "socs", "gpus", "cpus", "brands"];
26+
// Keep the chart legible if the history ever grows large: downsample to at most
27+
// MAX points, always preserving the first (baseline) and last (latest) commits.
28+
const MAX = 40;
29+
30+
function git(args) {
31+
return execFileSync("git", ["-C", REPO_ROOT, ...args], {
32+
encoding: "utf8",
33+
maxBuffer: 64 * 1024 * 1024,
34+
});
35+
}
36+
37+
function countsOf(manifest) {
38+
const counts = {};
39+
let total = 0;
40+
for (const key of ORDER) {
41+
const n = manifest?.collections?.[key]?.count;
42+
if (typeof n === "number") {
43+
counts[key] = n;
44+
total += n;
45+
}
46+
}
47+
return { counts, total };
48+
}
49+
50+
function downsample(points) {
51+
if (points.length <= MAX) return points;
52+
const step = (points.length - 1) / (MAX - 1);
53+
const picked = [];
54+
const seen = new Set();
55+
for (let i = 0; i < MAX; i++) {
56+
const idx = Math.round(i * step);
57+
if (!seen.has(idx)) {
58+
seen.add(idx);
59+
picked.push(points[idx]);
60+
}
61+
}
62+
return picked;
63+
}
64+
65+
function buildPoints() {
66+
// %H sha, %cI committer ISO date, %s subject — 0x1f-separated, one line/commit.
67+
const raw = git(["log", "--format=%H%x1f%cI%x1f%s", "--", TRACKED]).trim();
68+
if (!raw) return [];
69+
const commits = raw.split("\n").map((line) => {
70+
const [sha, date, ...rest] = line.split("\x1f");
71+
return { sha, date, title: rest.join("\x1f") };
72+
});
73+
// git log is newest-first; the timeline reads oldest-first.
74+
commits.reverse();
75+
76+
const points = [];
77+
for (const c of commits) {
78+
let manifest;
79+
try {
80+
manifest = JSON.parse(git(["show", `${c.sha}:${TRACKED}`]));
81+
} catch {
82+
continue; // file absent/unparseable at this commit — skip it
83+
}
84+
const { counts, total } = countsOf(manifest);
85+
if (!total) continue;
86+
points.push({
87+
sha: c.sha.slice(0, 7),
88+
date: c.date,
89+
title: (c.title || "Dataset sync").trim(),
90+
url: `${REPO_URL}/commit/${c.sha}`,
91+
total,
92+
counts,
93+
});
94+
}
95+
return downsample(points);
96+
}
97+
98+
function main() {
99+
let points = [];
100+
try {
101+
points = buildPoints();
102+
} catch (err) {
103+
console.warn(`[build-history] git history unavailable: ${err.message}`);
104+
}
105+
mkdirSync(dirname(OUT), { recursive: true });
106+
writeFileSync(
107+
OUT,
108+
JSON.stringify({ generated_at: new Date().toISOString(), schema: 1, points }, null, 2),
109+
);
110+
console.log(`[build-history] wrote ${points.length} point(s) -> ${OUT}`);
111+
}
112+
113+
main();

site/src/pages/index.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ const endpoints = [
116116
<div>
117117
<span class="kicker">00 - History</span>
118118
<h2 style="margin-top:14px">Dataset growth over time</h2>
119-
<p class="sec-sub">Recent dump commits are replayed into a small growth chart, showing how many records each sync added.</p>
119+
<p class="sec-sub">Every dump commit is replayed into a growth chart — from the first snapshot to the latest sync — showing how many records each one added.</p>
120120
</div>
121121
<span class="num">v1/index.json</span>
122122
</div>

site/src/scripts/techapi.js

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -288,12 +288,41 @@ function countUp(node, target) {
288288
}).join("");
289289
}
290290

291-
async function loadCommitHistory(currentManifest) {
292-
const commitsUrl = `https://api.github.com/repos/GetTechAPI/TechAPI/commits?path=${encodeURIComponent(dumpPath)}&per_page=8`;
291+
const fmtWhen = (date) => date
292+
? date.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" })
293+
: "recent";
294+
const rowsFromCounts = (counts) => order
295+
.map((key) => ({ key, count: counts?.[key] }))
296+
.filter((row) => row.count != null);
297+
298+
// Preferred path: a single prebuilt manifest (build-history.mjs) holding the
299+
// FULL dump timeline. No GitHub API, no per-commit fetches, no rate limit, and
300+
// old commits stay visible (the live API path only ever showed the last 7).
301+
async function pointsFromStaticHistory() {
302+
const data = await getJSON("v1/history.json");
303+
const points = (data.points || []).map((p) => {
304+
const date = p.date ? new Date(p.date) : null;
305+
return {
306+
sha: String(p.sha || "").slice(0, 7),
307+
dateValue: date ? date.getTime() : 0,
308+
when: fmtWhen(date),
309+
title: String(p.title || "Dataset sync").split("\n")[0],
310+
url: p.url || "https://github.com/GetTechAPI/TechAPI",
311+
rows: rowsFromCounts(p.counts),
312+
total: p.total != null ? p.total : rowsFromCounts(p.counts).reduce((s, r) => s + r.count, 0),
313+
};
314+
}).filter((p) => p.total > 0);
315+
return points.sort((a, b) => a.dateValue - b.dateValue);
316+
}
317+
318+
// Fallback (e.g. local `astro dev` with no prebuilt history.json): the old live
319+
// GitHub API replay, capped at the most recent commits.
320+
async function pointsFromGitHubApi() {
321+
const commitsUrl = `https://api.github.com/repos/GetTechAPI/TechAPI/commits?path=${encodeURIComponent(dumpPath)}&per_page=10`;
293322
const response = await fetch(commitsUrl);
294323
if (!response.ok) throw new Error(response.statusText);
295324
const commits = await response.json();
296-
const items = Array.isArray(commits) ? commits.slice(0, 7) : [];
325+
const items = Array.isArray(commits) ? commits.slice(0, 10) : [];
297326
const snapshots = await Promise.all(items.map(async (item) => {
298327
const sha = String(item.sha || "");
299328
const rawUrl = `https://raw.githubusercontent.com/GetTechAPI/TechAPI/${sha}/${dumpPath}`;
@@ -304,15 +333,19 @@ function countUp(node, target) {
304333
return {
305334
sha: sha.slice(0, 7),
306335
dateValue: date ? date.getTime() : 0,
307-
when: date ? date.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" }) : "recent",
336+
when: fmtWhen(date),
308337
title: (item.commit?.message || "Dataset sync").split("\n")[0],
309338
url: item.html_url || "https://github.com/GetTechAPI/TechAPI",
310339
rows: countRows(manifest),
311340
total: totalRecords(manifest),
312341
};
313342
}));
343+
return snapshots.filter(Boolean).sort((a, b) => a.dateValue - b.dateValue);
344+
}
314345

315-
const points = snapshots.filter(Boolean).sort((a, b) => a.dateValue - b.dateValue);
346+
async function loadCommitHistory(currentManifest) {
347+
let points = await pointsFromStaticHistory().catch(() => null);
348+
if (!points || !points.length) points = await pointsFromGitHubApi();
316349
if (!points.length) throw new Error("empty history");
317350

318351
const currentTotal = totalRecords(currentManifest);

0 commit comments

Comments
 (0)