Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/pr-metadata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ jobs:
if (trackers.size) {
const marker = '<!-- techapi-tracking -->';
const existing = pr.body || '';
const refs = [...trackers].sort().map((ref) => `- Refs ${ref}`).join('\n');
const refs = [...trackers].sort().map((ref) => `- Related to ${ref}`).join('\n');
const block = `${marker}\n\n## Tracking\n${refs}`;
const body = existing.includes(marker)
? existing.replace(new RegExp(`${marker}[\\s\\S]*$`), block)
Expand Down Expand Up @@ -160,6 +160,11 @@ jobs:
project_json="$(gh project view "$project_number" --owner "$owner" --format json)"
project_id="$(jq -r '.id' <<< "$project_json")"
item_id="$(gh project item-add "$project_number" --owner "$owner" --url "$PR_URL" --format json --jq '.id')"
status_field_id="$(gh project field-list "$project_number" --owner "$owner" --format json --jq '.fields[] | select(.name == "Status") | .id')"
status_option_id="$(gh project field-list "$project_number" --owner "$owner" --format json --jq '.fields[] | select(.name == "Status") | .options[] | select(.name == "In Progress") | .id')"
if [ -z "$status_option_id" ]; then
status_option_id="$(gh project field-list "$project_number" --owner "$owner" --format json --jq '.fields[] | select(.name == "Status") | .options[] | select(.name == "Todo") | .id')"
fi
start_field_id="$(gh project field-list "$project_number" --owner "$owner" --format json --jq '.fields[] | select(.name == "Start date") | .id')"
target_field_id="$(gh project field-list "$project_number" --owner "$owner" --format json --jq '.fields[] | select(.name == "Target date") | .id')"
priority_field_id="$(gh project field-list "$project_number" --owner "$owner" --format json --jq '.fields[] | select(.name == "Priority") | .id')"
Expand All @@ -168,6 +173,9 @@ jobs:
priority_option_id="$(gh project field-list "$project_number" --owner "$owner" --format json --jq '.fields[] | select(.name == "Priority") | .options[] | select(.name == "Medium") | .id')"
fi

if [ -n "$item_id" ] && [ -n "$status_field_id" ] && [ -n "$status_option_id" ]; then
gh project item-edit --id "$item_id" --project-id "$project_id" --field-id "$status_field_id" --single-select-option-id "$status_option_id"
fi
if [ -n "$item_id" ] && [ -n "$start_field_id" ]; then
gh project item-edit --id "$item_id" --project-id "$project_id" --field-id "$start_field_id" --date "$today"
fi
Expand Down
9 changes: 6 additions & 3 deletions site/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ const endpoints = [
<div class="sec-head">
<div>
<span class="kicker">00 - History</span>
<h2 style="margin-top:14px">Dataset growth, synced live</h2>
<p class="sec-sub">Counts come from the published static dump, while recent syncs come from repository activity when GitHub is reachable.</p>
<h2 style="margin-top:14px">Dataset growth over time</h2>
<p class="sec-sub">Recent dump commits are replayed into a small growth chart, showing how many records each sync added.</p>
</div>
<span class="num">v1/index.json</span>
</div>
Expand All @@ -124,7 +124,10 @@ const endpoints = [
<div class="history-grid" id="history-counts"></div>
</div>
<div class="history-panel">
<div class="history-label">Recent syncs</div>
<div class="history-label">Growth timeline</div>
<div class="history-chart" id="history-chart" aria-label="Dataset record growth chart">
<div class="history-empty">Loading growth chart...</div>
</div>
<ol class="history-list" id="history-list">
<li><span class="history-dot"></span><span>Loading repository history...</span></li>
</ol>
Expand Down
135 changes: 109 additions & 26 deletions site/src/scripts/techapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,46 +191,129 @@ function countUp(node, target) {
(function history() {
const totalEl = document.getElementById("history-total");
const countsEl = document.getElementById("history-counts");
const chartEl = document.getElementById("history-chart");
const listEl = document.getElementById("history-list");
if (!totalEl || !countsEl || !listEl) return;
if (!totalEl || !countsEl || !chartEl || !listEl) return;

const order = ["smartphones", "socs", "gpus", "cpus", "brands"];
const label = { smartphones: "Phones", socs: "SoCs", gpus: "GPUs", cpus: "CPUs", brands: "Brands" };
const shortLabel = { smartphones: "phones", socs: "socs", gpus: "gpus", cpus: "cpus", brands: "brands" };
const dumpPath = "site/public/v1/index.json";
const countRows = (manifest) => order
.map((key) => ({ key, count: manifest.collections?.[key]?.count }))
.filter((row) => row.count != null);
const totalRecords = (manifest) => countRows(manifest).reduce((sum, row) => sum + row.count, 0);
const sumByKey = (rows) => rows.reduce((out, row) => {
out[row.key] = row.count;
return out;
}, {});

getJSON("v1/index.json").then((manifest) => {
const rows = order
.map((key) => ({ key, count: manifest.collections?.[key]?.count }))
.filter((row) => row.count != null);
function renderSnapshot(manifest) {
const rows = countRows(manifest);
const total = rows.reduce((sum, row) => sum + row.count, 0);
totalEl.textContent = total.toLocaleString() + " records";
countsEl.innerHTML = rows.map((row) =>
`<div class="history-count"><span>${label[row.key]}</span><b>${row.count.toLocaleString()}</b></div>`
).join("");
}

function largestChanges(prevRows, nextRows) {
if (!prevRows) return [];
const prev = sumByKey(prevRows);
return nextRows
.map((row) => ({ key: row.key, delta: row.count - (prev[row.key] || 0) }))
.filter((row) => row.delta > 0)
.sort((a, b) => b.delta - a.delta)
.slice(0, 2);
}

function renderHistory(points) {
if (!points.length) throw new Error("empty history");
const maxTotal = Math.max(...points.map((point) => point.total));
const minTotal = Math.min(...points.map((point) => point.total));
const range = Math.max(1, maxTotal - minTotal);
chartEl.innerHTML = points.map((point) => {
const pct = 18 + ((point.total - minTotal) / range) * 82;
const deltaText = point.delta > 0 ? "+" + point.delta.toLocaleString() : "baseline";
return `<a class="history-bar" href="${esc(point.url)}" target="_blank" rel="noopener" style="--h:${pct.toFixed(1)}%" title="${esc(point.title)}">
<span class="history-bar-fill"></span>
<span class="history-bar-total">${point.total.toLocaleString()}</span>
<span class="history-bar-delta">${esc(deltaText)}</span>
</a>`;
}).join("");

listEl.innerHTML = points.slice().reverse().map((point) => {
const changes = point.changes.length
? point.changes.map((row) => `${shortLabel[row.key]} +${row.delta.toLocaleString()}`).join(", ")
: (point.delta > 0 ? `total +${point.delta.toLocaleString()}` : "baseline snapshot");
return `<li><span class="history-dot"></span><span>
<a href="${esc(point.url)}" target="_blank" rel="noopener">${esc(point.title)}</a>
<small>${esc(point.when)} - ${esc(point.sha)} - ${esc(changes)}</small>
</span></li>`;
}).join("");
}

async function loadCommitHistory(currentManifest) {
const commitsUrl = `https://api.github.com/repos/GetTechAPI/TechAPI/commits?path=${encodeURIComponent(dumpPath)}&per_page=8`;
const response = await fetch(commitsUrl);
if (!response.ok) throw new Error(response.statusText);
const commits = await response.json();
const items = Array.isArray(commits) ? commits.slice(0, 7) : [];
const snapshots = await Promise.all(items.map(async (item) => {
const sha = String(item.sha || "");
const rawUrl = `https://raw.githubusercontent.com/GetTechAPI/TechAPI/${sha}/${dumpPath}`;
const raw = await fetch(rawUrl);
if (!raw.ok) return null;
const manifest = await raw.json();
const date = item.commit?.committer?.date ? new Date(item.commit.committer.date) : null;
return {
sha: sha.slice(0, 7),
dateValue: date ? date.getTime() : 0,
when: date ? date.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" }) : "recent",
title: (item.commit?.message || "Dataset sync").split("\n")[0],
url: item.html_url || "https://github.com/GetTechAPI/TechAPI",
rows: countRows(manifest),
total: totalRecords(manifest),
};
}));

const points = snapshots.filter(Boolean).sort((a, b) => a.dateValue - b.dateValue);
if (!points.length) throw new Error("empty history");

const currentTotal = totalRecords(currentManifest);
const latest = points[points.length - 1];
if (latest.total !== currentTotal) {
points.push({
sha: "current",
dateValue: Date.now(),
when: "current",
title: "Current published snapshot",
url: base + "v1/index.json",
rows: countRows(currentManifest),
total: currentTotal,
});
}

for (let i = 0; i < points.length; i++) {
const prev = points[i - 1];
points[i].delta = prev ? points[i].total - prev.total : 0;
points[i].changes = largestChanges(prev?.rows, points[i].rows);
}
renderHistory(points);
}

getJSON("v1/index.json").then((manifest) => {
renderSnapshot(manifest);
return loadCommitHistory(manifest).catch(() => {
chartEl.innerHTML = '<div class="history-empty">Growth chart unavailable</div>';
listEl.innerHTML = '<li><span class="history-dot"></span><span>Current static dump is available; commit history could not be loaded.<small>GitHub API unavailable</small></span></li>';
});
}).catch(() => {
totalEl.textContent = "sync unavailable";
countsEl.innerHTML = '<div class="history-count"><span>Static dump</span><b>offline</b></div>';
chartEl.innerHTML = '<div class="history-empty">Growth chart unavailable</div>';
listEl.innerHTML = '<li><span class="history-dot"></span><span>Current static dump could not be loaded.<small>Build the public data first</small></span></li>';
});

fetch("https://api.github.com/repos/GetTechAPI/TechAPI/commits?path=site/public/v1&per_page=5")
.then((response) => {
if (!response.ok) throw new Error(response.statusText);
return response.json();
})
.then((commits) => {
const items = Array.isArray(commits) ? commits.slice(0, 4) : [];
if (!items.length) throw new Error("empty history");
listEl.innerHTML = items.map((item) => {
const message = (item.commit?.message || "Dataset sync").split("\n")[0];
const date = item.commit?.committer?.date ? new Date(item.commit.committer.date) : null;
const when = date ? date.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" }) : "recent";
const sha = String(item.sha || "").slice(0, 7);
const url = item.html_url || "https://github.com/GetTechAPI/TechAPI";
return `<li><span class="history-dot"></span><span><a href="${esc(url)}" target="_blank" rel="noopener">${esc(message)}</a><small>${esc(when)} · ${esc(sha)}</small></span></li>`;
}).join("");
})
.catch(() => {
listEl.innerHTML = '<li><span class="history-dot"></span><span>Current static dump is available; repository history could not be loaded.<small>GitHub API unavailable</small></span></li>';
});
})();

/* ============================================================
Expand Down
67 changes: 66 additions & 1 deletion site/src/styles/techapi.css
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,75 @@ code, .mono { font-family: var(--mono); }
.history-count b { color: var(--fg); font-size: 13px; }
.history-list {
list-style: none;
margin: 18px 0 0;
margin: 20px 0 0;
padding: 0;
display: grid;
gap: 14px;
}
.history-chart {
min-height: 210px;
margin-top: 18px;
padding: 16px 12px 12px;
display: grid;
grid-auto-flow: column;
grid-auto-columns: minmax(62px, 1fr);
align-items: end;
gap: 10px;
overflow-x: auto;
border: 1px solid var(--border);
border-radius: var(--radius);
background:
linear-gradient(to top, var(--border) 1px, transparent 1px) 0 25% / 100% 25%,
var(--surface-2);
}
.history-empty {
align-self: center;
justify-self: center;
grid-column: 1 / -1;
font-family: var(--mono);
color: var(--muted);
font-size: 12px;
}
.history-bar {
min-width: 0;
height: 178px;
display: grid;
grid-template-rows: 1fr auto auto;
gap: 6px;
color: var(--fg);
}
.history-bar-fill {
align-self: end;
min-height: 10px;
height: var(--h);
border: 1px solid color-mix(in srgb, var(--accent) 60%, var(--border));
border-radius: 4px 4px 2px 2px;
background:
linear-gradient(180deg, color-mix(in srgb, var(--accent) 92%, white 8%), var(--accent-deep));
box-shadow: 0 0 26px -12px var(--accent);
transition: filter .16s, transform .16s;
}
.history-bar:hover .history-bar-fill {
filter: brightness(1.08);
transform: translateY(-2px);
}
.history-bar-total,
.history-bar-delta {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: var(--mono);
text-align: center;
}
.history-bar-total {
font-size: 11px;
font-weight: 700;
}
.history-bar-delta {
font-size: 10px;
color: var(--accent-text);
}
.history-list li {
display: grid;
grid-template-columns: 12px 1fr;
Expand Down Expand Up @@ -331,6 +395,7 @@ code, .mono { font-family: var(--mono); }
@media (max-width: 760px) {
.history { grid-template-columns: 1fr; }
.history-grid { grid-template-columns: 1fr; }
.history-chart { grid-auto-columns: 68px; }
}

/* ============================================================
Expand Down
Loading