From f89b64256a0906dca70a5852983707e929d079ce Mon Sep 17 00:00:00 2001 From: puppe1990 Date: Sat, 19 Sep 2026 00:15:45 -0300 Subject: [PATCH] Hide unsettled GitHub traffic days from the catalog pulse GitHub finalizes a traffic day about 36h after it closes, so the newest buckets come back empty or partial. Summing them across the catalog left two phantom zero columns at the end of the pulse chart, which read as a broken sync even though the hourly SnapshotTraffic jobs were healthy. Filter out days inside the settle window so the pulse ends on finalized data, and note the delay in the chart caption. --- web/src/components/CatalogCharts.svelte | 5 +++-- web/src/lib/trafficCharts.js | 9 ++++++++ web/src/lib/trafficCharts.test.js | 29 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/web/src/components/CatalogCharts.svelte b/web/src/components/CatalogCharts.svelte index 49b3aa3..5f4aca3 100644 --- a/web/src/components/CatalogCharts.svelte +++ b/web/src/components/CatalogCharts.svelte @@ -2,6 +2,7 @@ import { formatCount } from "../lib/format.js"; import { catalogTrafficTotals, + cutUnsettledDays, dailyCatalogPulse, pulseBars, quietStarsRank, @@ -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, @@ -62,7 +63,7 @@

Last 14 days

-

Unique visitors and cloners summed across this catalog.

+

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.

{#each pulse as day} diff --git a/web/src/lib/trafficCharts.js b/web/src/lib/trafficCharts.js index f7843b8..a292e5d 100644 --- a/web/src/lib/trafficCharts.js +++ b/web/src/lib/trafficCharts.js @@ -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))}%`; diff --git a/web/src/lib/trafficCharts.test.js b/web/src/lib/trafficCharts.test.js index ba2cfd9..8d8f375 100644 --- a/web/src/lib/trafficCharts.test.js +++ b/web/src/lib/trafficCharts.test.js @@ -1,6 +1,7 @@ import { describe, expect, test } from "vitest"; import { catalogTrafficTotals, + cutUnsettledDays, dailyCatalogPulse, pulseBars, quietStarsRank, @@ -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));