You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@whimsical.apache.org by se...@apache.org on 2021/02/27 00:04:32 UTC

[whimsy] branch master updated: Nomination checker for board nominees

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

sebb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/whimsy.git


The following commit(s) were added to refs/heads/master by this push:
     new cb24b3e  Nomination checker for board nominees
cb24b3e is described below

commit cb24b3ef694440a63e801a5566f2ff03fbde7cd0
Author: Sebb <se...@apache.org>
AuthorDate: Sat Feb 27 00:04:24 2021 +0000

    Nomination checker for board nominees
---
 www/members/board-nominations.cgi | 161 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 161 insertions(+)

diff --git a/www/members/board-nominations.cgi b/www/members/board-nominations.cgi
new file mode 100755
index 0000000..c6f5919
--- /dev/null
+++ b/www/members/board-nominations.cgi
@@ -0,0 +1,161 @@
+#!/usr/bin/env ruby
+PAGETITLE = "Board nominations cross-check" # Wvisible:meeting
+$LOAD_PATH.unshift '/srv/whimsy/lib'
+
+require 'erb'
+require 'mail'
+require 'wunderbar/bootstrap'
+require 'whimsy/asf'
+require 'whimsy/asf/member-files'
+require_relative 'meeting-util'
+
+# link to members private-arch
+MBOX = 'https://mail-search.apache.org/members/private-arch/members/'
+
+# link to roster page
+ROSTER = '/roster/committer'
+MEETINGS = ASF::SVN['Meetings']
+MAIL_ROOT = '/srv/mail' # TODO: this should be config item
+
+# Encapsulate gathering data to improve error processing
+def setup_data
+  # get a list of current year's members@ emails
+  year = Time.new.year.to_s
+  archive = Dir[File.join(MAIL_ROOT, "members", "#{year}*", "*")]
+
+  # select messages that have a subject line starting with [MEMBER NOMINATION]
+  emails = []
+  archive.each do |email|
+    next if email.end_with? '/index'
+    message = IO.read(email, mode: 'rb')
+    next unless message[/^Date: .*/].to_s.include? year
+    subject = message[/^Subject: .*/]
+    next if not subject # HACK: allow script to continue if bogus email
+    next if subject =~ /board nominations$/ # not a nomination!
+    subjectUC = subject.upcase
+    next unless subjectUC =~ /BOARD/
+    next unless subjectUC =~ /NOMI[NM]ATION/
+    mail = Mail.new(message.encode(message.encoding, crlf_newline: true))
+    mail.subject.sub!(/\bDelacratez\b/, 'Delacretaz') # typo
+    emails << mail if mail.subject =~ /^\[?BOARD NOMI[MN]ATION\]?/i
+  end
+
+  # parse nominations for names and ids
+  nominations = ASF::MemberFiles.board_nominees.map do |id, hash|
+    {id: id, name: hash['Public Name'], nominator: hash['Nominated by']}
+  end
+
+  # preload names
+  people = ASF::Person.preload('cn',
+    nominations.map {|nominee| ASF::Person.find(nominee[:id])})
+
+  return nominations, people, emails
+end
+
+# produce HTML output of reports, highlighting ones that have not (yet)
+# been posted
+_html do
+  _style %{
+    .missing {background-color: yellow}
+    .flexbox {display: flex; flex-flow: row wrap}
+    .flexitem {flex-grow: 1}
+    .flexitem:first-child {order: 2}
+    .flexitem:last-child {order: 1}
+    .count {margin-left: 4em}
+  }
+  _body? do
+    _whimsy_body(
+      title: PAGETITLE,
+      related: {
+        ASF::SVN.svnpath!('Meetings') => 'Official Meeting Agenda Directory'
+      },
+      helpblock: -> {
+        _ 'This script checks board nomination statements from members@ against the official meeting ballot files, and highlights differences. '
+        _ 'This probably only works in the period shortly before or after a Members meeting!'
+        _br
+        _ 'Entries are highlighted if they are not present in both lists.'
+      }
+    ) do
+      cur_mtg_dir = File.basename(MeetingUtil.get_latest(MEETINGS))
+      nominations, people, emails = setup_data
+      _div.flexbox do
+        _div.flexitem do
+          _h1_! do
+            _ 'Nominees in '
+            _a 'svn', href: ASF::SVN.svnpath!('Meetings', cur_mtg_dir, 'board_nominations.txt')
+          end
+
+          _p.count "Count: #{nominations.count}"
+
+          _ul nominations.sort_by {|nominee| nominee[:name]} do |nominee|
+            _li! do
+              person = ASF::Person.find(nominee[:id])
+              # N.B. \b does not match if it follows ')', so won't match John (Fred)
+              # TODO: Work-round is to also look for EOS, but this needs to be improved
+              match = /\b(#{Regexp.escape(nominee[:name]||'')}|#{Regexp.escape(person.public_name||'')})(\b|$)/i
+
+              if emails.any? {|mail| mail.subject.downcase =~ match}
+                _a.present person.public_name, href: "#{ROSTER}/#{nominee[:id]}"
+              else
+                _a.missing person.public_name, href: "#{ROSTER}/#{nominee[:id]}"
+                _ ' Nominated by: '
+                _ nominee[:nominator]
+              end
+
+              if nominee[:name] != person.public_name
+                _span " (as #{nominee[:name]})"
+              end
+            end
+          end
+        end
+
+        nominees = nominations.map! {|person| person[:name]}
+        nominees += people.map {|person| person.public_name}
+
+        _div.flexitem do
+          _h1_.posted! do
+            _a "Posted", href:
+              'https://mail-search.apache.org/members/private-arch/members/'
+            _ " nominations reports"
+          end
+
+          _p.count "Count: #{emails.count}"
+
+          # attempt to sort reports by nominee name
+          emails.sort_by! do |mail|
+            mail.subject.downcase.gsub('- ', '').sub(/\[.*?\]\s*/, '')
+          end
+
+          # output an unordered list of subjects linked to the message archive
+          _ul emails do |mail|
+            _li do
+              # ERB::Util.url_encode changes space to %20 as required in the path component
+              href = MBOX + mail.date.strftime('%Y%m') + '.mbox/' +
+              ERB::Util.url_encode('<' + mail.message_id + '>')
+
+              # N.B. \b does not match if it follows ')', so won't match John (Fred)
+              # TODO: Work-round is to also look for EOS, but this needs to be improved
+              if nominees.any? {|name| mail.subject =~ /\b#{Regexp.escape(name)}(\b|$)/i}
+                _a.present mail.subject, href: href
+              else
+                _a.missing mail.subject, href: href
+                _ ' From: '
+                _ mail.from.first
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
+
+# produce JSON output of reports
+# N.B. This is activated if the ACCEPT header references 'json'
+_json do
+  _ reports do |mail| # TODO: reports is not defined
+    _subject mail.subject
+    _link MBOX + ERB::Util.url_encode('<' + mail.message_id + '>') # TODO looks wrong: does not agree with href above
+    _missing missing.any? {|title| mail.subject.downcase =~ /\b#{Regexp.escape(title)}\b/}
+  end
+end