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
5 changes: 3 additions & 2 deletions web/src/components/CatalogCharts.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { formatCount } from "../lib/format.js";
import {
catalogTrafficTotals,
cutUnsettledDays,
dailyCatalogPulse,
pulseBars,
quietStarsRank,
Expand All @@ -17,7 +18,7 @@
$: totals = catalogTrafficTotals(repos);
$: visitors = rankTraffic(repos, "view_uniques", 10);
$: cloners = rankTraffic(repos, "clone_uniques", 10);
$: pulse = pulseBars(dailyCatalogPulse(repos));
$: pulse = pulseBars(cutUnsettledDays(dailyCatalogPulse(repos)));
$: languages = uniqueVisitorsByLanguage(repos);
$: quiet = quietStarsRank(repos).map((row) => ({
...row,
Expand Down Expand Up @@ -62,7 +63,7 @@
<section class="chart-panel chart-panel-wide" aria-labelledby="chart-pulse">
<header class="chart-head">
<h3 id="chart-pulse">Last 14 days</h3>
<p>Unique visitors and cloners summed across this catalog.</p>
<p>Unique visitors and cloners summed across this catalog. GitHub settles traffic with a ~1–2 day delay, so the newest days appear once they are final.</p>
</header>
<div class="pulse-chart">
{#each pulse as day}
Expand Down
9 changes: 9 additions & 0 deletions web/src/lib/trafficCharts.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ export function dailyCatalogPulse(repos) {
return fillDaySpan(rows);
}

// GitHub finalizes a traffic day roughly 36h after it closes, so the newest
// buckets come back empty or partial. Hide them so the pulse ends on settled data.
const SETTLE_HOURS = 36;

export function cutUnsettledDays(days, now = new Date(), settleHours = SETTLE_HOURS) {
const cutoff = now.getTime() - settleHours * 3600 * 1000;
return (days || []).filter((day) => Date.parse(`${day.date}T00:00:00Z`) <= cutoff);
}

function pulseHeight(value, peak) {
if (value <= 0) return "0%";
return `${Math.max(6, Math.round((value / peak) * 100))}%`;
Expand Down
29 changes: 29 additions & 0 deletions web/src/lib/trafficCharts.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, test } from "vitest";
import {
catalogTrafficTotals,
cutUnsettledDays,
dailyCatalogPulse,
pulseBars,
quietStarsRank,
Expand Down Expand Up @@ -119,6 +120,34 @@ describe("dailyCatalogPulse", () => {
});
});

describe("cutUnsettledDays", () => {
const pulse = [
{ date: "2026-09-16", viewUniques: 5, cloneUniques: 2 },
{ date: "2026-09-17", viewUniques: 3, cloneUniques: 1 },
{ date: "2026-09-18", viewUniques: 0, cloneUniques: 0 },
{ date: "2026-09-19", viewUniques: 0, cloneUniques: 0 },
];

test("drops trailing days GitHub has not settled yet", () => {
const now = new Date("2026-09-19T03:00:00Z");
expect(cutUnsettledDays(pulse, now).map((day) => day.date)).toEqual(["2026-09-16", "2026-09-17"]);
});

test("keeps a day once it is older than the settle window", () => {
const now = new Date("2026-09-19T13:00:00Z");
expect(cutUnsettledDays(pulse, now).map((day) => day.date)).toEqual([
"2026-09-16",
"2026-09-17",
"2026-09-18",
]);
});

test("returns an empty list when every day is unsettled", () => {
const fresh = [{ date: "2026-09-19", viewUniques: 1, cloneUniques: 0 }];
expect(cutUnsettledDays(fresh, new Date("2026-09-19T03:00:00Z"))).toEqual([]);
});
});

describe("pulseBars", () => {
test("labels UTC days and scales both series to the same peak", () => {
const bars = pulseBars(dailyCatalogPulse(catalog));
Expand Down
Loading