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 8c04f272 board-nominate: add entry to board nominees
8c04f272 is described below

commit 8c04f272b5569014aaf0fb715ca2e60cd64d57d7
Author: Sebb <[email protected]>
AuthorDate: Sat Feb 18 23:25:39 2023 +0000

    board-nominate: add entry to board nominees
---
 lib/whimsy/asf/member-files.rb |  61 +++++++++++++++++++--
 www/members/board-nominate.cgi | 117 +++++++++++++++++++++++++++++++++++++++++
 www/members/index.cgi          |   3 +-
 www/members/nominations.cgi    |   3 +-
 www/members/watch.cgi          |   2 +-
 5 files changed, 180 insertions(+), 6 deletions(-)

diff --git a/lib/whimsy/asf/member-files.rb b/lib/whimsy/asf/member-files.rb
index 2ada7497..b529fcc7 100644
--- a/lib/whimsy/asf/member-files.rb
+++ b/lib/whimsy/asf/member-files.rb
@@ -9,7 +9,8 @@ module ASF
   class MemberFiles
 
     NOMINATED_MEMBERS = 'nominated-members.txt'
-
+    NOMINATED_BOARD = 'board_nominations.txt'
+  
     # get the latest meeting directory or nomination file
     def self.latest_meeting(name=nil)
       if name.nil? # we want the parent directory
@@ -74,7 +75,7 @@ module ASF
       end
     end
 
-    # create a nomination entry in the standard format
+    # create a member nomination entry in the standard format
     #
     def self.make_member_nomination(fields = {})
       availid = fields[:availid] or raise ArgumentError.new(":availid is 
required")
@@ -97,6 +98,27 @@ module ASF
       ].compact.join("\n") + "\n"
     end
 
+    # create a board nomination entry in the standard format
+    #
+    def self.make_board_nomination(fields = {})
+      availid = fields[:availid] or raise ArgumentError.new(":availid is 
required")
+      publicname = ASF::Person[availid]&.cn or raise 
ArgumentError.new(":availid #{availid} is invalid")
+      nomby = fields[:nomby] or raise ArgumentError.new(":nomby is required")
+      ASF::Person[nomby]&.dn or raise ArgumentError.new(":nomby is invalid")
+      secby = fields[:secby] || ''
+      statement = fields[:statement] or raise ArgumentError.new(":statement is 
required")
+      [
+        '',
+        "   #{publicname}",
+        "   Nominated by: #{nomby}@apache.org",
+        "   Seconded by: #{secby}",
+        '',
+        '   Nomination statement:',
+        ASFString.reflow(statement, 4, 80),
+        ''
+      ].compact.join("\n") + "\n"
+    end
+
     # Sort the member_nominees, optionally adding new entries
     def self.sort_member_nominees(contents, entries=nil)
       sections = contents.split(%r{^-{10,}\n})
@@ -119,6 +141,30 @@ module ASF
       [header, sections, 
''].join("-----------------------------------------\n")
     end
 
+    # Sort the board_nominees, optionally adding new entries
+    def self.sort_board_nominees(contents, entries=nil)
+      sections = contents.split(%r{^-{10,}\n})
+      header = sections.shift(2)
+      sections.pop if sections.last.strip == ''
+      sections.append(*entries) if entries # add new entries if any
+      names = {}
+      # replace 'each' by 'sort_by!' to sort by last name
+      sections.each do |s|
+        # sort by last name; check for duplicates
+        m = s.match %r{\s+(.+)}
+        if m
+          name = m[1]
+          raise ArgumentError.new("Duplicate id: #{name}") if names.include? 
name
+          names[name] = 1
+          name.split.last
+        else
+          'ZZ'
+        end
+      end
+      # reconstitute the file
+      [header, sections, ''].join("---------------------------------------\n")
+    end
+
     # update the member nominees
     def self.update_member_nominees(env, wunderbar, entries=nil, msg=nil, 
opt={})
       nomfile = latest_meeting(NOMINATED_MEMBERS)
@@ -128,6 +174,15 @@ module ASF
       end
     end
 
+    # update the board nominees
+    def self.update_board_nominees(env, wunderbar, entries=nil, msg=nil, 
opt={})
+      nomfile = latest_meeting(NOMINATED_BOARD)
+      opt[:diff] = true unless opt.include? :diff # default to true
+      ASF::SVN.update(nomfile, msg || 'Updating board nominations', env, 
wunderbar, opt) do |_tmpdir, contents|
+        sort_board_nominees(contents, entries)
+      end
+    end
+
     # TODO: change to return arrays rather than hash.
     # This would help detect duplicate entries
 
@@ -152,7 +207,7 @@ module ASF
     # Return hash of board nominees
     def self.board_nominees
       nominees = {}
-      ASF::MemberFiles.parse_file('board_nominations.txt') do |hdr, nominee|
+      ASF::MemberFiles.parse_file(NOMINATED_BOARD) do |hdr, nominee|
         # for board, the header currently looks like this:
         # <PUBLIC NAME>
         id = ASF::Person.find_by_name!(hdr) || hdr # default to full name
diff --git a/www/members/board-nominate.cgi b/www/members/board-nominate.cgi
new file mode 100755
index 00000000..906357fa
--- /dev/null
+++ b/www/members/board-nominate.cgi
@@ -0,0 +1,117 @@
+#!/usr/bin/env ruby
+PAGETITLE = "Add entries to board nomination file" # Wvisible:tools
+# Note: PAGETITLE must be double quoted
+
+$LOAD_PATH.unshift '/srv/whimsy/lib'
+require 'wunderbar'
+require 'wunderbar/bootstrap'
+require 'whimsy/asf'
+require 'whimsy/asf/forms'
+require 'whimsy/asf/member-files'
+require 'whimsy/asf/wunderbar_updates'
+
+def emit_form(title, prev_data)
+  _whimsy_panel(title, style: 'panel-success') do
+    _form.form_horizontal method: 'post' do
+      _whimsy_forms_subhead(label: 'Nomination Form')
+      field = 'availid'
+      _whimsy_forms_input(
+        label: 'Nominee availid', name: field,
+        value: prev_data[field], helptext: 'Enter the availid of the potential 
board member'
+      )
+      _whimsy_forms_input(
+        label: 'Nominated by', name: 'nomby', readonly: true, value: $USER
+      )
+      _whimsy_forms_input(
+        label: 'Seconded by', name: 'secby', helptext: 'Optional 
comma-separated list of seconds'
+      )
+      field = 'statement'
+      _whimsy_forms_input(label: 'Nomination statement', name: field, rows: 10,
+        value: prev_data[field], helptext: 'Reason for nomination'
+      )
+      _whimsy_forms_submit
+    end
+  end
+end
+
+# Validation as needed within the script
+# Returns: 'OK' or a text message describing the problem
+def validate_form(formdata: {})
+  uid = formdata['availid']
+  chk = ASF::Person[uid]&.asf_member?
+  chk.nil? and return "Invalid availid: #{uid}"
+  already = ASF::MemberFiles.board_nominees
+  return "Already nominated: #{uid} by #{already[uid]['Nominated by']}" if 
already.include? uid
+  return 'OK'
+end
+
+
+# Handle submission (checkout user's apacheid.json, write form data, checkin 
file)
+# @return true if we think it succeeded; false in all other cases
+def process_form(formdata: {}, wunderbar: {})
+  statement = formdata['statement']
+
+  _h3 'Copy of statement to put in an email (if necessary)'
+  _pre statement
+
+  _hr
+
+  _h3 'Transcript of update to nomination file'
+  uid = formdata['availid']
+  entry = ASF::MemberFiles.make_board_nomination({
+    availid: uid,
+    nomby: $USER,
+    secby: formdata['secby'],
+    statement: statement
+  })
+
+  environ = Struct.new(:user, :password).new($USER, $PASSWORD)
+  ASF::MemberFiles.update_board_nominees(environ, wunderbar, [entry], "+= 
#{uid}")
+  return true
+end
+
+# Produce HTML
+_html do
+  _body? do # The ? traps errors inside this block
+    _whimsy_body( # This emits the entire page shell: header, navbar, basic 
styles, footer
+      title: PAGETITLE,
+      subtitle: 'About This Script',
+      related: {
+        ASF::SVN.svnpath!('Meetings') => 'Official Meeting Agenda Directory'
+      },
+      helpblock: -> {
+        _h3 'BETA - please report any errors to the Whimsy PMC!'
+        _p %{
+          This form can be used to ADD entries to the board-nominations.txt 
file.
+          This is currently for use by the Nominator only, and does not send a 
copy
+          of the nomination to the members list.
+          There is currently no support for updating an existing entry.
+        }
+      }
+    ) do
+
+      _div id: 'nomination-form' do
+        if _.post?
+          submission = _whimsy_params2formdata(params)
+          valid = validate_form(formdata: submission)
+          if valid == 'OK'
+            if process_form(formdata: submission, wunderbar: _)
+              _p.lead "Thanks for Using This Form!"
+            else
+              _div.alert.alert_warning role: 'alert' do
+                _p "SORRY! Your submitted form data failed process_form, 
please try again."
+              end
+            end
+          else
+            _div.alert.alert_danger role: 'alert' do
+              _p "SORRY! Your submitted form data failed validate_form, please 
try again."
+              _p valid
+            end
+          end
+        else # if _.post?
+          emit_form('Enter nomination data', {})
+        end
+      end
+    end
+  end
+end
diff --git a/www/members/index.cgi b/www/members/index.cgi
index 15e75e1d..5fdbc0ec 100755
--- a/www/members/index.cgi
+++ b/www/members/index.cgi
@@ -12,9 +12,10 @@ MEETING = {
   'proxy.cgi' => "Assign a proxy for the (current) Member's meeting",
   'watch.cgi' => "Potential Member Watch List - tracking candidates for future 
nominations",
   'memberless-pmcs.cgi' => "Crosscheck PMCs with few/no ASF Members, for 
future nominations",
-  'member_nominations' => 'Update list of nominated members',
+  'member_nominations' => 'Add entries to list of nominated members',
   'nominations.cgi' => "Member's nominations cross-check - ensuring 
nominations get on the ballot, etc.",
   'board-nominations.cgi' => "Board nominations cross-check - ensuring 
nominations get on the ballot, etc.",
+  'board-nominate.cgi' => "Add entries to board nomination file",
   'attendance-xcheck.cgi' => "Member's Meeting Attendance cross-check - who 
attended when",
   'non-participants.cgi' => "Active Members not participating in recent 
meetings (to send a poll to)",
   'inactive.cgi' => "Poll of Inactive Members - tool to query 
non-participating members why",
diff --git a/www/members/nominations.cgi b/www/members/nominations.cgi
index e85628ae..d119a8bb 100755
--- a/www/members/nominations.cgi
+++ b/www/members/nominations.cgi
@@ -90,8 +90,9 @@ _html do
       related: {
         '/members/memberless-pmcs' => 'PMCs with no/few ASF Members',
         '/members/watch' => 'Watch list for potential Member candidates',
-        '/members/member_nominations' => 'Update list of nominated members',
+        '/members/member_nominations' => 'Add entries to list of nominated 
members',
         'board-nominations' => 'Board nominations cross-check',
+        'board-nominate.cgi' => "Add entries to board nomination file",
         ASF::SVN.svnpath!('Meetings') => 'Official Meeting Agenda Directory'
       },
       helpblock: -> {
diff --git a/www/members/watch.cgi b/www/members/watch.cgi
index b0334169..606dea31 100755
--- a/www/members/watch.cgi
+++ b/www/members/watch.cgi
@@ -20,7 +20,7 @@ _html do
       title: PAGETITLE,
       related: {
         '/members/memberless-pmcs' => 'PMCs with no/few ASF Members',
-        '/members/member_nominations' => 'Update list of nominated members',
+        '/members/member_nominations' => 'Add entries to list of nominated 
members',
         '/members/nominations' => 'Members Meeting Nomination Crosscheck',
         ASF::SVN.svnpath!('Meetings') => 'Official Meeting Agenda Directory'
       },

Reply via email to