Author: sebb
Date: Tue Mar  3 17:21:52 2026
New Revision: 1932140

Log:
Migrate to BAT from Whimsy

Added:
   comdev/reporter.apache.org/trunk/scripts/rapp/bat.py   (contents, props 
changed)
Deleted:
   comdev/reporter.apache.org/trunk/scripts/rapp/whimsy.py
Modified:
   comdev/reporter.apache.org/trunk/scripts/rapp/drafts.py
   comdev/reporter.apache.org/trunk/scripts/rapp/overview.py
   comdev/reporter.apache.org/trunk/scripts/wsgi.py
   comdev/reporter.apache.org/trunk/site/wizard/js/source/drafts.js
   comdev/reporter.apache.org/trunk/site/wizard/js/source/primer.js
   comdev/reporter.apache.org/trunk/site/wizard/js/source/unified.js
   comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js

Added: comdev/reporter.apache.org/trunk/scripts/rapp/bat.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ comdev/reporter.apache.org/trunk/scripts/rapp/bat.py        Tue Mar  3 
17:21:52 2026        (r1932140)
@@ -0,0 +1,193 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+""" script for publishing a report to BAT """
+# This is part of a gunicorn app (not intended for standalone use)
+
+import os
+import json
+import requests
+import re
+import pdata
+import committee_info
+
+BAT_BASE = 'https://agenda.apache.org'
+
+# read-only URLs
+BAT_CALENDAR = BAT_BASE + '/integration/reporter/calendar.json'
+BAT_COMMENTS = BAT_BASE + '/integration/reporter/historical-comments'
+BAT_AGENDA_IP = BAT_BASE + '/integration/reporter/agenda/%s.json'
+
+# Submit the report
+BAT_SUBMIT = BAT_BASE + '/integration/reporter/post'
+
+def get_bat(url, env, ttl = 3600):
+    cached = True
+    xurl = re.sub(r"[^a-z0-9]+", "-", url.replace('.json', ''))
+    wanted_file = '/tmp/%s.json' % xurl
+    if pdata.has_cache(wanted_file, ttl = ttl):
+        js = json.load(open(wanted_file))
+    else:
+        try:
+            print("Fetching %s => %s..." % (url, wanted_file))
+            rv = requests.get(url, headers = {'Authorization': 
env.get('HTTP_AUTHORIZATION')}, timeout = 5)
+            rv.raise_for_status()
+            js = rv.json()
+            with open(wanted_file, "w") as f:
+                json.dump(js, f)
+                f.close()
+                cached = False
+        except Exception as e: # fall back to cache on failure!
+            print(f"Failed to fetch {url}: {e}")
+            if os.path.exists(wanted_file):
+                print(f"Using stale cache for {wanted_file}")
+                js = json.load(open(wanted_file))
+            else:
+                print(f"No cache for {wanted_file}")
+                raise
+
+    return js, cached
+
+def latest_agenda(environ):
+    calendar, _cached = get_bat(BAT_CALENDAR, environ)
+    latest = calendar['agendas'][-1]
+    ymd = re.match(r"board_agenda_(\d\d\d\d_\d\d_\d\d)\.txt", latest)
+    if ymd:
+        return latest, BAT_AGENDA_IP % ymd.group(1).replace('_', '-')
+    else:
+        return latest, BAT_AGENDA_IP % 'latest'
+
+def has_access(user, project):
+    member = pdata.isASFMember(user)
+    pmc = project in pdata.getPMCs(user)
+    return (member or pmc)
+
+def guess_title(project):
+    """ Guess the whimsy name of a project """
+    pmcSummary = committee_info.PMCsummary()
+
+    # Figure out the name as written in whimsy..
+    pname = project
+    if project in pmcSummary:
+        pname = pmcSummary[project]['name'].replace('Apache ', '')
+
+    return pname
+
+# /api/bat/agenda/refresh
+def agenda_forced(environ, user):
+    """ Force BAT agenda refresh... """
+    _txtfile, url = latest_agenda(environ)
+    get_bat(url, environ, ttl = 0)
+    return agenda(environ, user)
+
+# /api/bat/agenda
+def agenda(environ, user, fproject = None):
+    """ Returns data on the board report for a project, IF present and/or 
filed in the current agenda """
+    project = fproject or environ.get('QUERY_STRING')
+    report = None
+    _txtfile, url = latest_agenda(environ)
+    if has_access(user, project):
+        agenda, _cached = get_bat(url, environ)
+        for entry in agenda:
+            # Ideally the agenda should include the bare project id, but it 
does not
+            # So we get it from the roster (could also use stats)
+            rid = entry.get('roster', 
'').replace('https://whimsy.apache.org/roster/committee/', '')
+            if project == rid:
+                report = entry
+                break
+
+        comments, _cached = get_bat(BAT_COMMENTS, environ)
+        title = report and report.get('title', guess_title(project))
+        if title in comments:
+            comments = comments[title]
+        else:
+            comments = {}
+
+        # Allow for empty agenda response
+        if len(agenda) > 0:
+            timestamp = agenda[0].get('timestamp', 0)
+        else:
+            timestamp = 0
+        return {
+            'can_access': True,
+            'found': report and True or False,
+            'filed': report and report.get('report') and True or False,
+            'report': report,
+            'comments': comments,
+            'timestamp': timestamp,
+            }
+
+    return {
+        'can_access': False,
+        'found': False,
+        }
+
+
+# /api/bat/comments
+def comments(environ, user):
+    """ Display board feedback from previous reports ... """
+    project = environ.get('QUERY_STRING')
+    comments, cached = get_bat(BAT_COMMENTS, environ)
+
+    # Figure out the name as written in whimsy..
+    pname = guess_title(project)
+
+    # If we can access, fetch comments
+    if comments and pname in comments and has_access(user, project):
+        comments = comments[pname]
+    else:
+        comments = {}
+
+    js = {
+        "pid": project,
+        "pname": pname,
+        "comments": comments,
+        "used_cache": cached,
+    }
+    return js
+
+
+# /api/bat/publish
+def publish(environ, _user):
+    try:
+        request_body_size = int(environ.get('CONTENT_LENGTH', 0))
+    except (ValueError):
+        request_body_size = 0
+    if request_body_size:
+        request_body = environ['wsgi.input'].read(request_body_size)
+        try:
+            js = json.loads(request_body.decode('utf-8'))
+        except:
+            js = {}
+    if js:
+        agenda = js.get('agenda')
+        project = js.get('project')
+        report = js.get('report')
+        digest = js.get('digest')
+        attach = js.get('attach')
+        print(project, agenda)
+        if agenda and project and report:
+            message = "Publishing report for %s via Reporter" % project
+            payload = {
+             'agenda': agenda,
+             'project': project,
+             'report': report,
+             'message': message,
+            }
+            if digest and attach:
+                del payload['project']
+                payload['attach'] = attach
+                payload['message'] = "Updating report for %s via Reporter." % 
project
+                payload['digest'] = digest
+            try:
+                rv = requests.post(BAT_SUBMIT, headers = {
+                    'Authorization': environ.get('HTTP_AUTHORIZATION'),
+                    "Content-Type": "application/json"
+                    }, json = payload, timeout = 10)
+                # Intercept client errors
+                if 400 <= rv.status_code < 500:
+                    return {'okay': False, 'message': rv.text}    
+                rv.raise_for_status()
+                return {'okay': True, 'message': "Posted to board agenda!"}
+            except:
+                pass
+    return {}

Modified: comdev/reporter.apache.org/trunk/scripts/rapp/drafts.py
==============================================================================
--- comdev/reporter.apache.org/trunk/scripts/rapp/drafts.py     Tue Mar  3 
16:44:46 2026        (r1932139)
+++ comdev/reporter.apache.org/trunk/scripts/rapp/drafts.py     Tue Mar  3 
17:21:52 2026        (r1932140)
@@ -7,13 +7,10 @@ import os
 import time
 import json
 import re
-import requests
 import pdata
-import rapp.whimsy
 
 DRAFTS_DIR = '/var/lib/rapp/drafts'
 EDITOR_TYPE = 'unified'
-WHIMSY_NOTIFY = 'https://whimsy.apache.org/board/agenda/json/reporter'
 
 if not os.path.isdir(DRAFTS_DIR):
     os.makedirs(DRAFTS_DIR, exist_ok = True)
@@ -98,15 +95,6 @@ def save(environ, user):
                 with open(os.path.join(DRAFTS_DIR, filename), "w") as f:
                     f.write(report)
                     f.close()
-                # Notify whimsy - this may fail, or not, we don't care :)
-                try:
-                    forgot = forgotten(environ, user)
-                    requests.post(WHIMSY_NOTIFY, headers = {
-                        'Authorization': environ.get('HTTP_AUTHORIZATION'),
-                        "Content-Type": "application/json"
-                        }, json = forgot, timeout = 2)
-                except:
-                    pass
                 return {
                     'okay': True,
                     'filename': filename,

Modified: comdev/reporter.apache.org/trunk/scripts/rapp/overview.py
==============================================================================
--- comdev/reporter.apache.org/trunk/scripts/rapp/overview.py   Tue Mar  3 
16:44:46 2026        (r1932139)
+++ comdev/reporter.apache.org/trunk/scripts/rapp/overview.py   Tue Mar  3 
17:21:52 2026        (r1932140)
@@ -7,7 +7,7 @@ import pdata
 import time
 import re
 import committee_info
-import rapp.whimsy
+import rapp.bat
 from datetime import datetime
 
 CACHE_TIMEOUT = 14400
@@ -54,7 +54,7 @@ def run(environ, user):
         # Check for filed reports
         dumps['filed'] = {}
         for k in allpmcs:
-            f = rapp.whimsy.agenda(environ, user, k)
+            f = rapp.bat.agenda(environ, user, k)
             dumps['filed'][k] = f.get('filed', False)
         
         dumps['you'] = committers[user]

Modified: comdev/reporter.apache.org/trunk/scripts/wsgi.py
==============================================================================
--- comdev/reporter.apache.org/trunk/scripts/wsgi.py    Tue Mar  3 16:44:46 
2026        (r1932139)
+++ comdev/reporter.apache.org/trunk/scripts/wsgi.py    Tue Mar  3 17:21:52 
2026        (r1932140)
@@ -9,15 +9,15 @@ import re
 CACHE_TIMEOUT = 14400
 
 import rapp.overview
-import rapp.whimsy
+import rapp.bat
 import rapp.drafts
 
 webmap = {
     '/api/overview': rapp.overview.run,
-    '/api/whimsy/comments': rapp.whimsy.comments,
-    '/api/whimsy/agenda': rapp.whimsy.agenda,
-    '/api/whimsy/agenda/refresh': rapp.whimsy.agenda_forced,
-    '/api/whimsy/publish': rapp.whimsy.publish,
+    '/api/bat/comments': rapp.bat.comments,
+    '/api/bat/agenda': rapp.bat.agenda,
+    '/api/bat/agenda/refresh': rapp.bat.agenda_forced,
+    '/api/bat/publish': rapp.bat.publish,
     '/api/drafts/index': rapp.drafts.index,
     '/api/drafts/save': rapp.drafts.save,
     '/api/drafts/fetch': rapp.drafts.fetch,

Modified: comdev/reporter.apache.org/trunk/site/wizard/js/source/drafts.js
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/source/drafts.js    Tue Mar 
 3 16:44:46 2026        (r1932139)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/source/drafts.js    Tue Mar 
 3 17:21:52 2026        (r1932140)
@@ -157,7 +157,7 @@ function publish_report() {
     document.getElementById('wrapper').style.display = 'none';
     document.getElementById("pname").style.display = 'none';
     
-    POST('/api/whimsy/publish', report_published, {}, formdata);
+    POST('/api/bat/publish', report_published, {}, formdata);
 }
 
 function report_published(state, json) {
@@ -178,7 +178,7 @@ function report_published(state, json) {
   }
   
   // Force whimsy reload of report meta data
-  GET("/api/whimsy/agenda/refresh?%s".format(project), prime_meta, {noreset: 
true});
+  GET("/api/bat/agenda/refresh?%s".format(project), prime_meta, {noreset: 
true});
     
 }
 

Modified: comdev/reporter.apache.org/trunk/site/wizard/js/source/primer.js
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/source/primer.js    Tue Mar 
 3 16:44:46 2026        (r1932139)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/source/primer.js    Tue Mar 
 3 17:21:52 2026        (r1932140)
@@ -27,7 +27,7 @@ function prime_wizard(state, json) {
     if (statsonly) {
         GET("/reportingcycles.json", prime_cycles, {});
     } else {
-        GET("/api/whimsy/agenda?%s".format(project), prime_meta, {})
+        GET("/api/bat/agenda?%s".format(project), prime_meta, {})
     }
 }
 

Modified: comdev/reporter.apache.org/trunk/site/wizard/js/source/unified.js
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/source/unified.js   Tue Mar 
 3 16:44:46 2026        (r1932139)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/source/unified.js   Tue Mar 
 3 17:21:52 2026        (r1932140)
@@ -271,11 +271,11 @@ function UnifiedEditor_compile() {
       text += "Your report could possibly use some more work, and that's okay! 
You can always save your current report as a draft and return later to work 
more on it. Drafts are saved for up to two months.";
     }
     else {
-        text += "That's it, your board report compiled a-okay and is 
potentially ready for submission! If you'd like more time to work on it, you 
can save it as a draft, and return later to make some final edits. Or you can 
publish it to the agenda via Whimsy.";
+        text += "That's it, your board report compiled a-okay and is 
potentially ready for submission! If you'd like more time to work on it, you 
can save it as a draft, and return later to make some final edits. Or you can 
publish it to the agenda via the BAT.";
     }
     text += "<br/><button class='btn btn-warning' onclick='save_draft();'>Save 
as draft</button>"
     if (!meta_data.found) {
-        text += " &nbsp; &nbsp; <button class='btn btn-secondary' disabled 
title='Your project is not listed in the current agenda!'>Publish via 
Whimsy</button>";
+        text += " &nbsp; &nbsp; <button class='btn btn-secondary' disabled 
title='Your project is not listed in the current agenda!'>Publish via the 
BAT</button>";
         // Is there a timestamp to check?
         let ts = 'timestamp' in meta_data ? meta_data.timestamp : 0
         if (ts > 0 && ts < new Date().valueOf()) {
@@ -284,8 +284,8 @@ function UnifiedEditor_compile() {
             text += "<br/><span style='color: maroon;'>Your project is not 
expected to report this month. You may save drafts but you cannot publish 
yet.</span>";        
         }
     }
-    else if (this.compiles) text += " &nbsp; &nbsp; <button 
onclick='publish_report();' class='btn btn-success'>Publish via Whimsy</button>"
-    else text += " &nbsp; &nbsp; <button class='btn btn-secondary' disabled 
title='Please fix the above issues before you can publish'>Publish via 
Whimsy</button>"
+    else if (this.compiles) text += " &nbsp; &nbsp; <button 
onclick='publish_report();' class='btn btn-success'>Publish via the 
BAT</button>"
+    else text += " &nbsp; &nbsp; <button class='btn btn-secondary' disabled 
title='Please fix the above issues before you can publish'>Publish via the 
BAT</button>"
     
     if (this.compiles) {
         text += "<hr/><h5>PLEASE MAKE SURE YOUR REPORT ADHERES TO THE <a 
target='_blank' 
href='https://www.apache.org/foundation/board/reporting#guidelines'>ASF 
REPORTING GUIDELINES</a> BEFORE SUBMITTING!</h5>";

Modified: comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js   Tue Mar  3 
16:44:46 2026        (r1932139)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js   Tue Mar  3 
17:21:52 2026        (r1932140)
@@ -1170,7 +1170,7 @@ function publish_report() {
     document.getElementById('wrapper').style.display = 'none';
     document.getElementById("pname").style.display = 'none';
     
-    POST('/api/whimsy/publish', report_published, {}, formdata);
+    POST('/api/bat/publish', report_published, {}, formdata);
 }
 
 function report_published(state, json) {
@@ -1191,7 +1191,7 @@ function report_published(state, json) {
   }
   
   // Force whimsy reload of report meta data
-  GET("/api/whimsy/agenda/refresh?%s".format(project), prime_meta, {noreset: 
true});
+  GET("/api/bat/agenda/refresh?%s".format(project), prime_meta, {noreset: 
true});
     
 }
 
@@ -1666,7 +1666,7 @@ function prime_wizard(state, json) {
     if (statsonly) {
         GET("/reportingcycles.json", prime_cycles, {});
     } else {
-        GET("/api/whimsy/agenda?%s".format(project), prime_meta, {})
+        GET("/api/bat/agenda?%s".format(project), prime_meta, {})
     }
 }
 
@@ -2695,11 +2695,11 @@ function UnifiedEditor_compile() {
       text += "Your report could possibly use some more work, and that's okay! 
You can always save your current report as a draft and return later to work 
more on it. Drafts are saved for up to two months.";
     }
     else {
-        text += "That's it, your board report compiled a-okay and is 
potentially ready for submission! If you'd like more time to work on it, you 
can save it as a draft, and return later to make some final edits. Or you can 
publish it to the agenda via Whimsy.";
+        text += "That's it, your board report compiled a-okay and is 
potentially ready for submission! If you'd like more time to work on it, you 
can save it as a draft, and return later to make some final edits. Or you can 
publish it to the agenda via the BAT.";
     }
     text += "<br/><button class='btn btn-warning' onclick='save_draft();'>Save 
as draft</button>"
     if (!meta_data.found) {
-        text += " &nbsp; &nbsp; <button class='btn btn-secondary' disabled 
title='Your project is not listed in the current agenda!'>Publish via 
Whimsy</button>";
+        text += " &nbsp; &nbsp; <button class='btn btn-secondary' disabled 
title='Your project is not listed in the current agenda!'>Publish via the 
BAT</button>";
         // Is there a timestamp to check?
         let ts = 'timestamp' in meta_data ? meta_data.timestamp : 0
         if (ts > 0 && ts < new Date().valueOf()) {
@@ -2708,8 +2708,8 @@ function UnifiedEditor_compile() {
             text += "<br/><span style='color: maroon;'>Your project is not 
expected to report this month. You may save drafts but you cannot publish 
yet.</span>";        
         }
     }
-    else if (this.compiles) text += " &nbsp; &nbsp; <button 
onclick='publish_report();' class='btn btn-success'>Publish via Whimsy</button>"
-    else text += " &nbsp; &nbsp; <button class='btn btn-secondary' disabled 
title='Please fix the above issues before you can publish'>Publish via 
Whimsy</button>"
+    else if (this.compiles) text += " &nbsp; &nbsp; <button 
onclick='publish_report();' class='btn btn-success'>Publish via the 
BAT</button>"
+    else text += " &nbsp; &nbsp; <button class='btn btn-secondary' disabled 
title='Please fix the above issues before you can publish'>Publish via the 
BAT</button>"
     
     if (this.compiles) {
         text += "<hr/><h5>PLEASE MAKE SURE YOUR REPORT ADHERES TO THE <a 
target='_blank' 
href='https://www.apache.org/foundation/board/reporting#guidelines'>ASF 
REPORTING GUIDELINES</a> BEFORE SUBMITTING!</h5>";

Reply via email to