Author: sebb Date: Sat Feb 7 17:34:22 2026 New Revision: 1931747 Log: Replace Whimsy with BAT
Added: comdev/reporter.apache.org/branches/tooling-project/scripts/rapp/bat.py (contents, props changed) Deleted: comdev/reporter.apache.org/branches/tooling-project/scripts/rapp/whimsy.py Modified: comdev/reporter.apache.org/branches/tooling-project/DOCKER.md comdev/reporter.apache.org/branches/tooling-project/compose.yml comdev/reporter.apache.org/branches/tooling-project/scripts/rapp/drafts.py comdev/reporter.apache.org/branches/tooling-project/scripts/rapp/overview.py comdev/reporter.apache.org/branches/tooling-project/scripts/wsgi.py comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/source/drafts.js comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/source/primer.js comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/source/unified.js comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/wizard.js Modified: comdev/reporter.apache.org/branches/tooling-project/DOCKER.md ============================================================================== --- comdev/reporter.apache.org/branches/tooling-project/DOCKER.md Sat Feb 7 17:33:40 2026 (r1931746) +++ comdev/reporter.apache.org/branches/tooling-project/DOCKER.md Sat Feb 7 17:34:22 2026 (r1931747) @@ -29,7 +29,7 @@ The `.env` file should not be readable o e.g. use `chmod 600 .env` on macOS/Linux Also be careful that it does not get added to any repository. -Browse to ```http://localhost/``` +Browse to ```http://localhost/:81``` You will be prompted to login. The credentials are not validated initially, but if they are incorrect, then no data will be displayed. Modified: comdev/reporter.apache.org/branches/tooling-project/compose.yml ============================================================================== --- comdev/reporter.apache.org/branches/tooling-project/compose.yml Sat Feb 7 17:33:40 2026 (r1931746) +++ comdev/reporter.apache.org/branches/tooling-project/compose.yml Sat Feb 7 17:34:22 2026 (r1931747) @@ -10,4 +10,4 @@ services: REPORTER_JIRA_USER: ${REPORTER_JIRA_USER:-githubbot} REPORTER_JIRA_PASSWORD: ${REPORTER_JIRA_PASSWORD:?} ports: - - 80:80 + - 81:80 Added: comdev/reporter.apache.org/branches/tooling-project/scripts/rapp/bat.py ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ comdev/reporter.apache.org/branches/tooling-project/scripts/rapp/bat.py Sat Feb 7 17:34:22 2026 (r1931747) @@ -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' +BAT_BASE = 'https://agenda-dev.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 = 14400): + 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') + # Kludge for webservices + project = pdata.ldapmap.get(project, project) + report = None + _txtfile, url = latest_agenda(environ) + if has_access(user, project): + agenda, _cached = get_bat(url, environ, ttl = 3600) + titleguess = guess_title(project) + for entry in agenda: + if titleguess == entry.get('title'): + report = entry + break + + comments, _cached = get_bat(BAT_COMMENTS, environ) + title = report and report.get('title', titleguess) + 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) + # Kludge for webservices + project = pdata.ldapmap.get(project, project) + 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) + rv.raise_for_status() + return {'okay': True, 'message': "Posted to board agenda!"} + except: + pass + return {} Modified: comdev/reporter.apache.org/branches/tooling-project/scripts/rapp/drafts.py ============================================================================== --- comdev/reporter.apache.org/branches/tooling-project/scripts/rapp/drafts.py Sat Feb 7 17:33:40 2026 (r1931746) +++ comdev/reporter.apache.org/branches/tooling-project/scripts/rapp/drafts.py Sat Feb 7 17:34:22 2026 (r1931747) @@ -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/branches/tooling-project/scripts/rapp/overview.py ============================================================================== --- comdev/reporter.apache.org/branches/tooling-project/scripts/rapp/overview.py Sat Feb 7 17:33:40 2026 (r1931746) +++ comdev/reporter.apache.org/branches/tooling-project/scripts/rapp/overview.py Sat Feb 7 17:34:22 2026 (r1931747) @@ -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/branches/tooling-project/scripts/wsgi.py ============================================================================== --- comdev/reporter.apache.org/branches/tooling-project/scripts/wsgi.py Sat Feb 7 17:33:40 2026 (r1931746) +++ comdev/reporter.apache.org/branches/tooling-project/scripts/wsgi.py Sat Feb 7 17:34:22 2026 (r1931747) @@ -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/branches/tooling-project/site/wizard/js/source/drafts.js ============================================================================== --- comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/source/drafts.js Sat Feb 7 17:33:40 2026 (r1931746) +++ comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/source/drafts.js Sat Feb 7 17:34:22 2026 (r1931747) @@ -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) { @@ -174,7 +174,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/branches/tooling-project/site/wizard/js/source/primer.js ============================================================================== --- comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/source/primer.js Sat Feb 7 17:33:40 2026 (r1931746) +++ comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/source/primer.js Sat Feb 7 17:34:22 2026 (r1931747) @@ -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/branches/tooling-project/site/wizard/js/source/unified.js ============================================================================== --- comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/source/unified.js Sat Feb 7 17:33:40 2026 (r1931746) +++ comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/source/unified.js Sat Feb 7 17:34:22 2026 (r1931747) @@ -275,7 +275,7 @@ function UnifiedEditor_compile() { } 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/branches/tooling-project/site/wizard/js/wizard.js ============================================================================== --- comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/wizard.js Sat Feb 7 17:33:40 2026 (r1931746) +++ comdev/reporter.apache.org/branches/tooling-project/site/wizard/js/wizard.js Sat Feb 7 17:34:22 2026 (r1931747) @@ -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) { @@ -1187,7 +1187,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}); } @@ -1662,7 +1662,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,7 +2695,7 @@ function UnifiedEditor_compile() { } 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()) { @@ -2704,8 +2704,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>";
