This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git


The following commit(s) were added to refs/heads/main by this push:
     new ff99a32  Fix stale snapshots in security-tracker-stats-dashboard (now, 
is_open, event cache) (#448)
ff99a32 is described below

commit ff99a32b085b696a600aba28617dc02560bddb23
Author: Jarek Potiuk <[email protected]>
AuthorDate: Thu Jun 4 04:39:47 2026 +0200

    Fix stale snapshots in security-tracker-stats-dashboard (now, is_open, 
event cache) (#448)
    
    Three independent causes made the dashboard diverge from the tracker's
    current state:
    
    1. render.py hard-coded NOW to 2026-05-21, freezing every snapshot at that
       date. Use datetime.now(timezone.utc).
    2. labels_open_at derived is_open only from replayed closed/reopened events;
       GitHub's issue-events API omits the closed event for some (often older)
       issues, leaving them stuck open. Backstop is_open with the authoritative
       closedAt from the issue payload (the cum_closed series already trusts 
it).
    3. fetch_events.py never refreshed cached event files, so issues relabeled 
or
       closed after their first fetch kept stale history. Re-fetch when the 
cached
       file is older than the issue's updatedAt.
    
    Generated-by: Claude Opus 4.8 (1M context)
---
 tools/security-tracker-stats-dashboard/fetch_events.py | 17 ++++++++++++++++-
 tools/security-tracker-stats-dashboard/render.py       |  9 ++++++++-
 2 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/tools/security-tracker-stats-dashboard/fetch_events.py 
b/tools/security-tracker-stats-dashboard/fetch_events.py
index a496b67..6488572 100644
--- a/tools/security-tracker-stats-dashboard/fetch_events.py
+++ b/tools/security-tracker-stats-dashboard/fetch_events.py
@@ -4,6 +4,7 @@
 import json
 import subprocess
 import concurrent.futures
+import datetime as dt
 import os
 
 ROOT = os.environ.get('TRACKER_STATS_CACHE', '/tmp/tracker-stats-cache')
@@ -14,6 +15,16 @@ with open(f'{ROOT}/issues.json') as f:
     issues = json.load(f)
 
 numbers = [i['number'] for i in issues]
+# Map issue number -> last-updated epoch so the cache can be refreshed when an
+# issue was relabeled / closed after its events were last fetched.
+def _iso_to_epoch(s):
+    if not s:
+        return None
+    try:
+        return dt.datetime.fromisoformat(s.replace('Z', '+00:00')).timestamp()
+    except ValueError:
+        return None
+UPDATED_AT = {i['number']: _iso_to_epoch(i.get('updatedAt')) for i in issues}
 print(f"Fetching events for {len(numbers)} issues...")
 
 os.makedirs(EVENTS_DIR, exist_ok=True)
@@ -21,7 +32,11 @@ os.makedirs(EVENTS_DIR, exist_ok=True)
 def fetch_one(n):
     out_path = f'{EVENTS_DIR}/{n}.json'
     if os.path.exists(out_path) and os.path.getsize(out_path) > 0:
-        return (n, True, 'cached')
+        # Trust the cache only if it was written after the issue was last
+        # updated; otherwise the issue changed since and the events are stale.
+        upd = UPDATED_AT.get(n)
+        if upd is None or os.path.getmtime(out_path) >= upd:
+            return (n, True, 'cached')
     try:
         r = subprocess.run(
             ['gh', 'api', f'repos/{REPO}/issues/{n}/events',
diff --git a/tools/security-tracker-stats-dashboard/render.py 
b/tools/security-tracker-stats-dashboard/render.py
index ccf971f..42c3cff 100644
--- a/tools/security-tracker-stats-dashboard/render.py
+++ b/tools/security-tracker-stats-dashboard/render.py
@@ -382,7 +382,7 @@ if UPSTREAM_REPO:
         with open(prs_path) as f:
             prs_cache = json.load(f)
 
-NOW = dt.datetime(2026, 5, 21, 0, 0, 0, tzinfo=dt.timezone.utc)
+NOW = dt.datetime.now(dt.timezone.utc)
 
 if UPSTREAM_REPO:
     # Match the original literal in the body-parse regex so an upstream
@@ -586,6 +586,13 @@ def labels_open_at(issue, ts):
             is_open = False
         elif e['event'] == 'reopened':
             is_open = True
+    # The issue-events API can omit the 'closed' event for some (often
+    # older) issues, which would leave is_open stuck True and miscount a
+    # closed issue as open. Trust the authoritative closedAt from the
+    # issue payload as a backstop (the cum_closed series already does).
+    closed_at = parse_dt(issue.get('closedAt'))
+    if issue.get('state') == 'CLOSED' and closed_at and closed_at <= ts:
+        is_open = False
     return labels, is_open
 
 

Reply via email to