www/members/nominate_board.cgi (227 lines of code) (raw):
#!/usr/bin/env ruby
PAGETITLE = "Nominate someone for the Board" # Wvisible:meeting
$LOAD_PATH.unshift '/srv/whimsy/lib'
require 'time'
require 'wunderbar'
require 'wunderbar/bootstrap'
require 'whimsy/asf'
require 'whimsy/asf/forms'
require 'whimsy/asf/member-files' # See for nomination file parsing
require 'whimsy/asf/wunderbar_updates'
require 'whimsy/asf/meeting-util'
require 'whimsy/asf/time-utils'
require 'mail'
MAILING_LIST = 'members@apache.org'
def emit_form(title, prev_data)
_whimsy_panel(title, style: 'panel-success') do
_form.form_horizontal method: 'post' do
field = 'availid'
# Construct list of active voting Members
ldap_members = ASF.members
ASF::Person.preload('id', ldap_members)
members_txt = ASF::Member.list
already = ASF::MemberFiles.board_nominees
field_list = {}
# Fillup dropdown with all active members
ldap_members.sort_by(&:public_name).each do |nominee|
next unless members_txt[nominee.id] # Non-members
next if members_txt[nominee.id]['status'] # Emeritus/Deceased
next if already.include? nominee.id # Previously nominated
field_list["#{nominee.id}"] = "#{nominee.public_name}"
end
_whimsy_forms_select(
label: 'Select Nominee',
name: field,
multiple: false,
values: prev_data[field],
options: field_list,
helptext: 'Select the name of a Member to nominate for the Board election'
)
_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 availids; ONLY if you have confirmed with the seconds directly'
)
field = 'statement'
_whimsy_forms_input(label: 'Nomination Statement', name: field, rows: 10,
value: prev_data[field], helptext: 'Explain why you believe this person would be a good Director'
)
_whimsy_forms_submitwrap(
noicon: true, label: 'submit', name: 'submit', value: 'submit', helptext: 'Checkin this nomination and send email to members@'
)
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']
return "You MUST provide a nomination statement for Candidate #{uid}; blank was provided!" if formdata['statement'].empty?
chk = ASF::Person[uid]&.asf_member?
chk.nil? and return "Invalid availid or non-Member nominated; please add manually if desired: (#{uid})\n\nYour Statement:\n#{formdata['statement']}"
already = ASF::MemberFiles.board_nominees
return "Candidate #{uid} has already been nominated by #{already[uid]['Nominated by']}" if already.include? uid
return 'OK'
end
# Handle submission (checkout board_nominations.txt, write form data, checkin file)
# Also creates a board_ballot/availid.txt file for director statements
# @return true if we think it succeeded; false in all other cases
def process_form(formdata: {}, wunderbar: {})
_h3 "Transcript of updates to nomination files #{ASF::MemberFiles::NOMINATED_BOARD} for candidate #{formdata['availid']}"
entry = ASF::MemberFiles.make_board_nomination({
availid: formdata['availid'],
nomby: formdata['nomby'],
secby: formdata['secby'],
statement: formdata['statement']
})
environ = Struct.new(:user, :password).new($USER, $PASSWORD)
ASF::MemberFiles.add_board_ballot(environ, wunderbar, "#{formdata['availid']}", "board_ballot/ += #{formdata['availid']}")
ASF::MemberFiles.update_board_nominees(environ, wunderbar, [entry], "+= #{formdata['availid']}")
return true
end
# Send email to members@ with this nomination's data
# Reports status to user in a _div
def send_nomination_mail(formdata: {})
uid = formdata['availid']
nomby = formdata['nomby']
public_name = ASF::Person.new(uid).public_name
secby = formdata.fetch('secby', nil)
secby.nil? || secby.empty? ? nomseconds = '' : nomseconds = "Nomination seconded by: #{secby}"
mail_body = <<-MAILBODY
This nomination for #{public_name} (#{uid}) as a Director
Nominee has been added:
#{formdata['statement']}
#{nomseconds}
--
- #{ASF::Person[nomby].public_name}
Email generated by Whimsy (#{File.basename(__FILE__)})
MAILBODY
# See check_boardnoms.cgi which parses this in list archives
mailsubject = "[BOARD NOMINATION] #{ASF::Person.new(uid).public_name} (#{uid})"
ASF::Mail.configure
mail = Mail.new do
to MAILING_LIST
bcc 'notifications@whimsical.apache.org'
from "#{ASF::Person[nomby].public_name} <#{nomby}@apache.org>"
subject mailsubject
text_part do
body mail_body
end
end
begin
mail.deliver!
rescue StandardError => e
_div.alert.alert_danger role: 'alert' do
_p.strong "ERROR: email was NOT sent due to: #{e.message} #{e.backtrace[0]}"
_p do
_ "To: #{MAILING_LIST}"
_br
_ "Subject: #{mailsubject}"
_br
_ "#{mail_body}"
end
end
return
end
_div.alert.alert_success role: 'alert' do
_p "The following email was sent:"
_p do
_ "To: #{MAILING_LIST}"
_br
_ "Subject: #{mailsubject}"
_br
_ "#{mail_body}"
end
end
return
end
# Produce HTML
_html do
_body? do
# Countdown until nominations for current meeting close
latest_meeting_dir = ASF::MeetingUtil.latest_meeting_dir
timelines = ASF::MeetingUtil.get_timeline(latest_meeting_dir)
t_now = Time.now.to_i
t_end = Time.parse(timelines['nominations_close_iso']).to_i
nomclosed = t_now > t_end
_whimsy_body(
title: PAGETITLE,
subtitle: 'About This Script',
related: {
'meeting' => 'Member Meeting FAQ and info',
'check_boardnoms.cgi' => 'Cross-check existing Board nominations',
'https://www.apache.org/foundation/governance/board' => 'Role of the Board of Directors',
ASF::SVN.svnpath!('Meetings') => 'Official Meeting Agenda Directory'
},
helpblock: -> {
_b "For: #{timelines['meeting_type']} Meeting on: #{timelines['meeting_iso']}"
_p do
_ %Q{
Use this form to nominate any Member for the ASF Board of Director election.
It automatically adds a properly formatted nomination to the #{ASF::MemberFiles::NOMINATED_BOARD} file,
and will then
}
_strong "send an email to the #{MAILING_LIST} list"
_ ' from you with the nomination, '
_a 'as is tradition.', href: 'https://lists.apache.org/list?members@apache.org:2023-2:%22BOARD%20NOMINATION%22'
_ 'This form only supports adding new nominations; to add seconds or comments, please use SVN.'
end
}
) do
if nomclosed
_h1 'Nominations are now closed!'
_p 'Sorry, no further nominations will be accepted for ballots at this meeting.'
else
_h3 "Nominations close in #{ASFTime.secs2text(t_end - t_now)} at #{Time.at(t_end).utc} for Meeting: #{timelines['meeting_iso']}"
end
_div id: 'nomination-form' do
if _.post?
unless nomclosed
submission = _whimsy_params2formdata(params)
valid = validate_form(formdata: submission)
end
if nomclosed
_div.alert.alert_warning role: 'alert' do
_p "Nominations have closed"
end
elsif valid == 'OK'
if process_form(formdata: submission, wunderbar: _)
_div.alert.alert_success role: 'alert' do
_p "Your nomination was submitted to svn; now sending email to #{MAILING_LIST}."
end
mailval = send_nomination_mail(formdata: submission)
_pre mailval
else
_div.alert.alert_danger role: 'alert' do
_p do
_span.strong "ERROR: Form data invalid in process_form(), update was NOT submitted!"
_br
_ "#{submission}"
end
end
end
else
_div.alert.alert_danger role: 'alert' do
_p do
_span.strong "ERROR: Form data invalid in validate_form(), update was NOT submitted!"
_br
_p valid
end
end
end
else # if _.post?
if nomclosed
_p do
_ 'Sorry, no further nominations will be accepted for ballots at this meeting.'
_br
_a 'See existing nominations.', href: '/members/check_boardnoms.cgi'
end
else
emit_form('Enter your nomination for a Director Nominee', {})
end
end
end
end
end
end