You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@community.apache.org by se...@apache.org on 2026/03/03 17:21:53 UTC
svn commit: r1932140 - in comdev/reporter.apache.org/trunk: scripts scripts/rapp site/wizard/js site/wizard/js/source
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 += " <button class='btn btn-secondary' disabled title='Your project is not listed in the current agenda!'>Publish via Whimsy</button>";
+ text += " <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 += " <button onclick='publish_report();' class='btn btn-success'>Publish via Whimsy</button>"
- else text += " <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 += " <button onclick='publish_report();' class='btn btn-success'>Publish via the BAT</button>"
+ else text += " <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 += " <button class='btn btn-secondary' disabled title='Your project is not listed in the current agenda!'>Publish via Whimsy</button>";
+ text += " <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 += " <button onclick='publish_report();' class='btn btn-success'>Publish via Whimsy</button>"
- else text += " <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 += " <button onclick='publish_report();' class='btn btn-success'>Publish via the BAT</button>"
+ else text += " <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>";