feat(forgejo-metrics): adjust line stats caching strategy and improve retry logic for contributor stats
This commit is contained in:
parent
c04ab6ac8b
commit
dd9681925e
|
|
@ -36,7 +36,8 @@ if TYPE_CHECKING:
|
|||
# ---------------------------------------------------------------------------
|
||||
_line_stats_cache: dict[str, tuple[float, int, int, bool]] = {}
|
||||
_line_stats_fetching: set[str] = set()
|
||||
_LINE_STATS_TTL = 300 # seconds before a re-fetch is triggered
|
||||
_LINE_STATS_TTL_HIT = 300 # 5 min — re-fetch cadence once real data is cached
|
||||
_LINE_STATS_TTL_MISS = 30 # 30 s — retry cadence while Forgejo is still computing
|
||||
|
||||
|
||||
async def _bg_fetch_line_stats(
|
||||
|
|
@ -383,8 +384,9 @@ async def get_forgejo_heatmap(
|
|||
cached = _line_stats_cache.get(cache_key)
|
||||
now = _time.monotonic()
|
||||
|
||||
ttl = _LINE_STATS_TTL_HIT if (cached and cached[3]) else _LINE_STATS_TTL_MISS
|
||||
if cache_key not in _line_stats_fetching and (
|
||||
cached is None or now - cached[0] > _LINE_STATS_TTL
|
||||
cached is None or now - cached[0] > ttl
|
||||
):
|
||||
# Normalise base_url the same way get_forgejo_client() does, eagerly,
|
||||
# so the background task never touches a potentially-closed session.
|
||||
|
|
|
|||
|
|
@ -353,28 +353,22 @@ class ForgejoAPIClient:
|
|||
async def get_contributor_stats(self, owner: str, repo: str) -> tuple[list[dict], bool]:
|
||||
"""Fetch per-contributor weekly stats for a repository.
|
||||
|
||||
Returns (contributors, has_data). On the first call Forgejo may return
|
||||
HTTP 202 ("computing") — we wait 2 s and retry once so the stats are
|
||||
available on the next dashboard load even if not this one.
|
||||
Returns (contributors, has_data). Forgejo returns HTTP 202 while it
|
||||
computes stats — that is not an error, it just means the caller should
|
||||
retry later. has_data=False signals the 202 case; the application layer
|
||||
uses a short retry TTL so it probes again within ~30 s.
|
||||
|
||||
Each contributor has a ``weeks`` array with ``w`` (Unix timestamp of
|
||||
week start), ``a`` (additions), and ``d`` (deletions).
|
||||
Each contributor dict has a ``weeks`` array with ``w`` (Unix timestamp),
|
||||
``a`` (additions), and ``d`` (deletions).
|
||||
"""
|
||||
import asyncio as _asyncio
|
||||
|
||||
client = await self._get_client()
|
||||
url = f"/api/v1/repos/{owner}/{repo}/stats/contributors"
|
||||
response = await client.get(url)
|
||||
|
||||
response = await client.get(
|
||||
f"/api/v1/repos/{owner}/{repo}/stats/contributors"
|
||||
)
|
||||
if response.status_code == 202:
|
||||
# Forgejo is computing — wait briefly then try once more
|
||||
await _asyncio.sleep(2)
|
||||
response = await client.get(url)
|
||||
|
||||
if response.status_code == 202:
|
||||
return [], False # still computing after retry
|
||||
return [], False # still computing — caller will retry shortly
|
||||
if response.status_code == 404:
|
||||
return [], True # no data, but not a 202 — treat as "has_data"
|
||||
return [], True # repo exists but no commits; treat as data-present
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
return (data if isinstance(data, list) else []), True
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ interface ForgejoHeatmapProps {
|
|||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const CELL = 13;
|
||||
const GAP = 3;
|
||||
const CELL = 16;
|
||||
const GAP = 4;
|
||||
const STRIDE = CELL + GAP;
|
||||
const WEEKS = 27;
|
||||
const LEFT = 28;
|
||||
const TOP = 18;
|
||||
const LEFT = 32;
|
||||
const TOP = 20;
|
||||
|
||||
const MONTHS = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ const LEVEL_FILL = [
|
|||
const VIOLET = "rgba(139,92,246,1)";
|
||||
const GREEN = "rgba(52,211,153,1)";
|
||||
const RED = "rgba(248,113,113,1)";
|
||||
const LABEL_CLR = "rgba(139,92,246,0.65)";
|
||||
const LABEL_CLR = "rgba(255,255,255,0.70)"; // white labels
|
||||
|
||||
function toLevel(count: number): number {
|
||||
if (count === 0) return 0;
|
||||
|
|
@ -166,23 +166,15 @@ export function ForgejoHeatmap({
|
|||
<span>More</span>
|
||||
</div>
|
||||
|
||||
{/* ── Contributions badge ──────────────────────────────────── */}
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className="inline-flex items-baseline gap-2 rounded-full px-5 py-2"
|
||||
style={{
|
||||
background: "rgba(139,92,246,0.10)",
|
||||
border: "1px solid rgba(139,92,246,0.25)",
|
||||
}}
|
||||
>
|
||||
{/* ── Contributions summary ────────────────────────────────── */}
|
||||
<p className="text-center">
|
||||
<span className="text-2xl font-bold tabular-nums" style={{ color: VIOLET }}>
|
||||
{totalEvents.toLocaleString()}
|
||||
</span>
|
||||
<span className="text-sm font-medium" style={{ color: LABEL_CLR }}>
|
||||
<span className="ml-1.5 text-sm" style={{ color: "rgba(255,255,255,0.70)" }}>
|
||||
contributions across all tracked repositories in the last 6 months
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
{/* ── Line stats ──────────────────────────────────────────── */}
|
||||
{hasLineStats ? (
|
||||
|
|
|
|||
Loading…
Reference in New Issue