www/members/meeting.cgi (336 lines of code) (raw):
#!/usr/bin/env ruby
PAGETITLE = "Member's Meeting Information" # Wvisible:meeting
$LOAD_PATH.unshift '/srv/whimsy/lib'
require 'whimsy/asf'
require 'wunderbar/bootstrap'
require 'time'
require 'json'
require 'wunderbar/jquery/stupidtable'
require 'whimsy/asf/meeting-util'
DTFORMAT = '%A, %d %B %Y at %H:%M %z'
TADFORMAT = '%Y%m%dT%H%M%S'
WDAYFORMAT = '%A'
# Utility function for links, Note: cheezy path detection within MEETING_FILES
def emit_link(cur_mtg_dir, f, desc)
_a desc, href: f.include?('/') && !f.start_with?('runbook/') ? f : File.join(cur_mtg_dir, f)
end
# Output action links for meeting records, depending on if current or past
# @param dt Time meeting starts
def emit_meeting(cur_mtg_dir, svn_mtg_dir, dt, num_members, quorum_need, num_proxies, attend_irc)
_div id: "meeting-#{dt.year}"
_whimsy_panel("All Meeting Details for #{dt.strftime(DTFORMAT)}", style: 'panel-info') do
if Time.now > dt
_p do
_ 'At the time of this past meeting, we had:'
_ul do
_li "#{num_members} eligible voting Members,"
_li "#{quorum_need} needed for quorum (one third),"
_li "#{num_proxies} proxy assignments available for the meeting,"
_li "And hoped that at least #{attend_irc} would attend the start of meeting."
end
attendees_file = File.join(cur_mtg_dir, 'attend')
if File.exist?(attendees_file)
attendees = File.readlines(attendees_file)
_ "By the end of the meeting, we had a total of #{attendees.count} Members participating (either via attending IRC, sending a proxy, or voting via email)"
else
_p.alert.alert_danger do
_span "Unable to calculate participating members ("
_code "attend"
_span "file does not yet exist for meeting)"
end
end
end
_p "These are historical links to the past meeting's record."
else
_p "Live links to the upcoming meeting records/ballots/how-tos are below."
end
_ul do
ASF::MeetingUtil::MEETING_FILES.each do |f, desc|
_li do
emit_link(svn_mtg_dir, f, desc)
end
end
end
end
end
MEETINGS = ASF::SVN['Meetings']
RECORDS_DIR = ASF::MeetingUtil::RECORDS
# produce HTML
_html do
_body? do
last_mtg_dir = ASF::MeetingUtil.get_latest_completed(MEETINGS)
last_mtg_date = Time.parse(File.basename(last_mtg_dir))
cur_mtg_dir = ASF::MeetingUtil.get_latest(MEETINGS)
meeting = File.basename(cur_mtg_dir)
svn_mtg_dir = File.join(RECORDS_DIR, meeting)
# Calculate quorum
num_members, quorum_need, num_proxies, attend_irc = ASF::MeetingUtil.calculate_quorum(cur_mtg_dir)
proxy_nominees = ASF::MeetingUtil.getProxyNominees.count
mtg_timeline = ASF::MeetingUtil.get_timeline(cur_mtg_dir)
meeting_start_time = Time.parse(mtg_timeline['meeting_start_iso'])
meeting_type = mtg_timeline.fetch('meeting_type', 'Annual')
_whimsy_body(
title: PAGETITLE,
subtitle: 'Member Meeting Overview',
relatedtitle: 'Meeting Explainers',
related: {
'/members/proxy' => 'PLEASE Assign A Proxy For The Meeting',
"#{svn_mtg_dir}/README.txt" => "Detailed README for this meeting",
"#{svn_mtg_dir}/agenda.txt" => "Actual Agenda for this meeting",
'https://www.apache.org/foundation/governance/meetings' => 'Descriptions: how voting works:',
'https://www.apache.org/foundation/governance/meetings#how-member-votes-are-tallied' => 'New Members Elected By Majority',
'https://www.apache.org/foundation/governance/meetings#how-votes-for-the-board-are-tallied' => 'Board Seats Are Elected By STV',
'/members/whatif' => 'Explore Past Board STV Results',
'/members/non-participants' => 'Members Not Participating Recently',
'/members/inactive' => 'Inactive Member Feedback Form',
RECORDS_DIR => 'Official Past Meeting Records',
'https://lists.apache.org/list.html?members@apache.org' => 'Read the members@ List Archives'
},
helpblock: -> {
if Time.now > meeting_start_time
_p do
_ %{
The last Member's Meeting was held #{last_mtg_date.strftime('%A, %d %B %Y')}. Expect the
next Annual Member's meeting to be scheduled between 12 - 13 months after the previous Annual meeting, as per
}
_a 'the bylaws 3.2.', href: 'https://www.apache.org/foundation/bylaws.html#3.2'
_ 'Stay tuned for a [NOTICE] '
_a 'email on members-notify@', href: 'https://lists.apache.org/list?members-notify@apache.org'
_ ' announcing the next meeting. The below information is about the '
_strong 'LAST'
_ " Member's meeting."
end
else
_p do
if /test/i =~ meeting_type
_strong "NOTICE NOTICE NOTICE: This is TEST MEETING DATA ONLY - NOT AN ACTUAL MEETING :NOTICE NOTICE NOTICE"
_br
end
_ "The next #{meeting_type} Member's Meeting starts at "
_a href: "http://www.timeanddate.com/worldclock/fixedtime.html?iso=#{meeting_start_time.strftime(TADFORMAT)}" do
_span.glyphicon.glyphicon_time ''
_ " #{meeting_start_time.strftime(DTFORMAT)} "
end
_ 'as an online meeting in the'
_strong ' NEW '
_a 'ASFMM.apache.org chat tool', href: 'https://asfmm.apache.org'
_ ' for less than an hour. Most activity happens in the weeks before the meeting. Please review the meeting timeline, and some NEW PROCESS steps, or '
_a 'see detailed FAQ about meeting files.', href: "#{svn_mtg_dir}/README.txt"
if /test/i =~ meeting_type
_br
_strong "NOTICE NOTICE NOTICE: This is TEST MEETING DATA ONLY - NOT AN ACTUAL MEETING :NOTICE NOTICE NOTICE"
end
end
_ul do
_li do
_a 'Nominations Open', href: '#nominations'
_ ": #{mtg_timeline['nominations_open_date']} - Seconds and Statements may be added"
end
_li "Nominations Close: #{mtg_timeline['nominations_close_date']} - no further nominations allowed"
_li do
_a 'Record Date set', href: '#seconds'
_ ": #{mtg_timeline['nominations_notice_date']} - New Member candidates list sent to members-notify@"
end
_li "Election Locked: #{mtg_timeline['vote_create_date']} - no further changes to Seconds, Statements"
_li do
_a 'Polls Open', href: '#recess'
_ ": #{mtg_timeline['vote_open_date']} - vote online at vote.apache.org"
end
_li "Polls Close: #{mtg_timeline['polls_close_date']} - vote early!"
_li do
_a 'Online Meeting', href: '#secondhalf'
_ ": #{mtg_timeline['meeting_iso']} - quorum required here; expect a short meeting"
end
_li do
_a 'New Member Applications', href: '#after'
_ " due before: #{mtg_timeline['member_form_date']}"
end
end
_p do
_ 'Currently, we will need '
_span.text_primary attend_irc
_ " Members who have NOT submitted a proxy to login to the online meeting #{meeting_start_time.strftime(WDAYFORMAT)} to reach quorum and continue the meeting."
_br
_ "Calculation: Total voting members: #{num_members}; one third for quorum: #{quorum_need}; minus previously submitted proxies: #{num_proxies}"
end
end
}
) do
help, copypasta = ASF::MeetingUtil.is_user_proxied(cur_mtg_dir, $USER)
user = ASF::Person.find($USER)
_div id: 'personal'
_whimsy_panel("#{user.public_name} - Personal Details For Meeting #{meeting_start_time.strftime(DTFORMAT)}", style: 'panel-primary') do
_p do
if help
_p help
if copypasta
_ul.bg_success do
copypasta.each do |copyline|
_pre copyline
end
end
end
else
_ 'You have not submitted a proxy - even if you can attend the the meeting, '
_strong 'please'
_a " assign a proxy - it's easy!", href: '/members/proxy'
end
end
end
_div id: 'nominations'
_whimsy_panel("Timeline: Nominations and Seconds Period", style: 'panel-default') do
_p do
_ 'Before an Annual meeting, Members may nominate candidates for the Board election, or as New Member Candidates. Nominations are only official if placed in the correct files; although much discussion also happens on members@.'
_ul do
['/members/nominate_board.cgi',
'/members/nominate_member.cgi',
'/members/proxy.cgi',
'/members/check_boardnoms.cgi',
'/members/check_membernoms.cgi'].each do |f|
_li do
emit_link(svn_mtg_dir, f, ASF::MeetingUtil::MEETING_FILES[f])
end
end
end
_ 'During this period, Members may add Seconds Statements of their own personal recommendations to various nominees.'
_ 'Director Nominees should decide to accept the nomination, and then '
_a 'add a Candidate Statement to their ballot', href: "#{svn_mtg_dir}/runbook/director_ballot_email.txt"
_br
_strong "Nominations close at: #{mtg_timeline['nominations_close_date']}!"
end
end
_div id: 'seconds'
_whimsy_panel("Timeline: Record Date and Nominees NOTICE email (10 days before meeting)", style: 'panel-default') do
_p do
_ "Record date: #{mtg_timeline['nominations_notice_date']}"
end
_p do
_ '10 days before the meeting, per bylaws 3.7 the official list of eligible Members is fixed, and the NOTICE of any candidates nominated for Membership will be sent out to members-notify@.'
_ 'No further names may be added to nominations, and director candidates should have '
_a 'added their Candidate Statement to their ballot', href: "#{svn_mtg_dir}/runbook/director_ballot_email.txt"
_ ' before the official ballots are frozen.'
_ul do
['agenda.txt',
'/members/check_boardnoms.cgi',
'/members/check_membernoms.cgi'].each do |f|
_li do
emit_link(svn_mtg_dir, f, ASF::MeetingUtil::MEETING_FILES[f])
end
end
end
_strong "Ballot files are frozen for vote creation at: #{mtg_timeline['vote_create_date']}!"
end
end
_div id: 'firsthalf' # Pre-2022 meeting anchor
_div id: 'recess'
_whimsy_panel("Timeline: Voting By Email (until 24 hours before meeting)", style: 'panel-info') do
_p do
_strong 'NEW PROCESS simplified vote.apache.org process!'
_ "Polls will open #{mtg_timeline['vote_open_date']}, and the vote monitors will send eligible voters an email "
_code 'From: voter@apache.org'
_ ' with updated instructions on voting.'
_ 'Voting is now done by simply logging into vote.apache.org with your Apache ID - no more need for a voting key in email!'
end
_p do
_strong 'REMEMBER:'
_ "Ballots close at #{mtg_timeline['polls_close_date']} - ONE FULL DAY (24 hours) BEFORE the meeting starts - don't wait to vote!"
end
_p do
_ul do
_li do
_a 'New Members are Elected By Majority Yes/No/Abstain vote, when elections held', href: 'https://www.apache.org/foundation/governance/meetings#membervoting'
end
_li do
_a 'Board Seats Are Elected By STV at Annual Meetings - ORDER OF YOUR VOTE MATTERS!', href: 'https://www.apache.org/foundation/governance/meetings#boardvoting'
end
_li do
_a 'Cast Your Votes (requires ASF Member Login)', href: "#{mtg_timeline['meeting_vote_link']}"
end
end
_strong "Polls close: #{mtg_timeline['polls_close_date']}!"
end
end
_div id: 'secondhalf'
_whimsy_panel("Timeline: Online Meeting (at #{meeting_start_time.strftime(DTFORMAT)})", style: 'panel-primary') do
_p do
_a href: "http://www.timeanddate.com/worldclock/fixedtime.html?iso=#{meeting_start_time.strftime(TADFORMAT)}" do
_span.glyphicon.glyphicon_time ''
_em '(meeting time in various zones)'
end
_ 'The single online meeting on asfmm.apache.org is typically short - it\'s primarily briefly reporting from officers, announcing vote results and any last-minute announcements. Members do not need to attend the meeting if you proxied or voted; all results will be emailed or checked into SVN.'
_ul do
_li do
_a 'ASFMM meeting tool (requires ASF Member Login)', href: 'https://asfmm.apache.org/'
end
['record', 'attend', 'voter-tally', 'raw_board_votes.txt', 'raw-irc-log'].each do |f|
_li do
emit_link(svn_mtg_dir, f, ASF::MeetingUtil::MEETING_FILES[f])
end
end
_li do
_a 'What-If tool for analyzing Board STV votes', href: '/members/whatif'
end
end
end
_p do
_ 'Note: If quorum is not achieved within 15 minutes, the online meeting may be rescheduled - be sure to submit a proxy!'
_ 'Various data files about the meeting (raw-irc-log, board voting tally if present) will be checked in soon after the meeting for historical records.'
_ 'Votes for the Omnibus resolution, if any, are included in raw-irc-log. We do not publish vote results for new member nominees.'
end
end
_div id: 'after'
_whimsy_panel("Timeline: After The Meeting", style: 'panel-default') do
_p do
_ 'Shortly after the live ASFMM Meeting ends, '
_a '@TheASF twitter', href: 'https://twitter.com/theasf'
_ ' will formally announce the new board, if an Annual meeting has elected one - please wait to retweet the official announcement.'
_span.text_warning 'IMPORTANT:'
_ ' Do NOT publicise the names of newly elected members! In rare cases, the new candidate might not accept the honor.'
end
_p do
_span.text_primary 'If you nominated a new member:'
_ ' You '
_strong 'must'
_ ' send an email with '
_a 'foundation/membership-application-email.txt', href: ASF::SVN.svnpath!('foundation', 'membership-application-email.txt')
_ ' to formally invite the new member to fill out the application form; follow instructions carefully. Applications must be signed and submitted to the secretary within 30 days of the meeting to be valid.'
end
_p do
_ "REMEMBER: newly elected members must return their application before #{mtg_timeline['member_form_date']}, otherwise they are not admitted per bylaws 4.1."
end
_p do
_ 'You can use the following page to '
_a 'check the progress of member invitations', href: '/members/check_invitations'
end
end
# Most/all of these links should already be included above
emit_meeting(cur_mtg_dir, svn_mtg_dir, meeting_start_time, num_members, quorum_need, num_proxies, attend_irc)
_div id: 'meeting-history'
_whimsy_panel("Member Meeting History", style: 'panel-info') do
all_mtg = Dir[File.join(MEETINGS, '19*'), File.join(MEETINGS, '2*')].sort
_p do
_ %{
The ASF has held #{all_mtg.count} Member's meetings in our
history. Some were Annual meetings, where we elect a new board;
a handful were Special mid-year meetings where we mostly just
elected new Members.
}
_ ' Remember, member meeting minutes are '
_span.text_warning 'private'
_ ' to the ASF. You can see your '
_a 'your own Attendance history at meetings.', href: '/members/inactive#attendance'
_ 'Various data files and tools tracking Attendance at meetings are in '
_code 'foundation/Meetings/attend*'
_ul do
all_mtg.each do |mtg|
_li do
tmp = File.join(RECORDS_DIR, File.basename(mtg))
_a tmp, href: tmp
end
end
end
end
end
end
end
end