blob: 6bbdc5852f2582f31eef95782243dd2b0d0ff118 [file] [log] [blame]
#!/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'
ICS_FILE = 'ASF-members-meeting.ics' # see Meetings/meeting-template/
ERROR_DATE = Time.new(1970, 1, 1) # An obvious error value 8-)
# Return Time from DTSTART in an .ics file
def ics2dtstart(f)
begin
tmp = IO.readlines(f).find{ |i| i =~ /DTSTART;TZID=UTC:/ }.split(':')[1].strip
return Time.parse(tmp)
rescue StandardError
return ERROR_DATE
end
end
# 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 : File.join(cur_mtg_dir, f)
end
# Output action links for meeting records, depending on if current or past
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']
# 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(ASF::MeetingUtil::RECORDS, meeting)
# mtg_date = Time.parse(meeting)
today = Time.now
# Calculate quorum
num_members, quorum_need, num_proxies, attend_irc = ASF::MeetingUtil.calculate_quorum(cur_mtg_dir)
# Use ics files for accurate times; see create-meeting.rb; note change in process 2022/2023
ics_date = ics2dtstart(File.join(cur_mtg_dir, ICS_FILE))
ROSTER = "/roster/committer"
_whimsy_body(
title: PAGETITLE,
subtitle: 'Member Meeting Overview',
relatedtitle: 'Meeting How-Tos',
related: {
'/members/proxy' => 'PLEASE Assign A Proxy For The Meeting',
'https://www.apache.org/foundation/governance/meetings' => 'How Meetings & 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',
ASF::MeetingUtil::RECORDS => 'Official Past Meeting Records',
'https://lists.apache.org/list.html?members@apache.org' => 'Read members@ List Archives'
},
helpblock: -> {
if ics_date == ERROR_DATE
_p do
_center do
_strong 'Note: .ics file seems to be missing!'
_br
_ 'Expecting to find:'
_br
_ul do
_li ICS_FILE
_li "svn_mtg_dir = #{svn_mtg_dir}, cur_mtg_dir = #{cur_mtg_dir},"
end
end
end
end
if today > ics_date # Based on the reconvene date
_p do
_ %{
The last Annual Member's Meeting was held #{last_mtg_date.strftime('%A, %d %B %Y')}. Expect the
next Member's meeting to be scheduled between 12 - 13 months after the previous meeting, as per
}
_a 'https://www.apache.org/foundation/bylaws.html#3.2', 'the bylaws 3.2.'
_ 'Stay tuned for a [NOTICE] email on members-notify@ announcing the next meeting. The below information is about the '
_strong 'LAST'
_ " Member's meeting."
end
else
_p do
_ "The Member's Meeting starts at "
_a href: "http://www.timeanddate.com/worldclock/fixedtime.html?iso=#{ics_date.strftime(TADFORMAT)}" do
_span.glyphicon.glyphicon_time ''
_ " #{ics_date.strftime(DTFORMAT)} "
end
_ "as an online IRC meeting for about an hour. REMEMBER: all voting is done by email the week BEFORE the IRC meeting"
end
_p do
_ 'Please read below for a Timeline of Meeting activities and links to how you can take action, or see additional links to the right. '
_strong 'REMINDER: '
_ 'Nominations for the board or new members close 10 days before the meeting starts; no new names may be added after that date.'
end
_p do
_ 'Currently, we will need '
_span.text_primary attend_irc
_ " Members attending the first half of the meeting on Tuesday and respond to Roll Call to reach quorum and continue the meeting."
_ " Calculation: Total voting members: #{num_members}, with one third for quorum: #{quorum_need}, minus previously submitted proxies: #{num_proxies}"
end
_p 'Individual Members are considered to have Attended a meeting if they either: respond to Roll Call (in first or second half of meeting); submit a proxy (that gets submitted during Roll Call); or who cast a ballot on any matters.'
_ul do
_li 'Estimated 2023 dates:'
_li 'Nominations open: 28-Jan'
_li 'Nominations close: 25-Feb'
_li 'Ballots emailed: 2-Mar'
_li 'Voting closes: 8-Mar (single IRC meeting on 9-Mar'
end
end
}
) do
# if there is a new meeting in the offing, use its date
# Old logic for two half meeting m1_date = mtg_date if m1_date == ERROR_DATE && cur_mtg_dir > last_mtg_dir
help, copypasta = ASF::MeetingUtil.is_user_proxied(cur_mtg_dir, $USER)
# attendance = JSON.parse(IO.read(File.join(MEETINGS, 'attendance.json')))
user = ASF::Person.find($USER)
_div id: 'personal'
_whimsy_panel("#{user.public_name} - Personal Details For Meeting #{ics_date.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 first half of the meeting, '
_a "please assign a proxy - it's easy!", href: '/members/proxy'
end
end
_p do
_span.text_warning 'REMINDER: '
_ "Voting ballots are sent to your official email address as found in members.txt, please double-check it is correct!"
_a 'See members.txt', href: ASF::SVN.svnpath!('foundation', 'members.txt')
end
end
_div id: 'nominations'
_whimsy_panel("Timeline: Nomination Period (until 10 days BEFORE meeting)", style: 'panel-default') do
_p do
_ 'Before the meeting, any Member may nominate people either for the Board, or as a New Member Candidate. Much of this discussion happens on members@ mailing list. Remember, all new nominated names must be checked into SVN 10 days before the meeting.'
_ 'Also, you should submit a proxy if you might not attend the the meeting.'
_ul do
['board_ballot.txt', 'nominated-members.txt', '/members/proxy.cgi'].each do |f|
_li do
emit_link(svn_mtg_dir, f, ASF::MeetingUtil::MEETING_FILES[f])
end
end
end
end
end
_div id: 'seconds'
_whimsy_panel("Timeline: Seconds Period (last ten days before meeting)", style: 'panel-default') do
_p do
_ 'The last 10 days before the meeting, you may add seconds (comments of support) to existing nomination files, but no new nominations are allowed.'
_ 'Also, you can still submit a proxy if you might not attend the the meeting.'
_ul do
['nominated-members.txt', '/members/proxy.cgi'].each do |f|
_li do
emit_link(svn_mtg_dir, f, ASF::MeetingUtil::MEETING_FILES[f])
end
end
end
end
end
_div id: 'firsthalf' # Pre-2022 meeting anchor
_div id: 'recess'
_whimsy_panel("Timeline: Voting By Email (week before meeting)", style: 'panel-info') do
_p do
_ 'One week before the meeting, the STeVe vote monitors will send you an email '
_code 'From: voter@apache.org'
_ ' with your voting key URL.'
_ 'All voting is done via a simple web interface at vote.apache.org after you login with your Apache ID.'
_b 'REMEMBER:'
_ "Ballots close ONE FULL DAY (24 hours) BEFORE the meeting starts - don't wait to vote!"
_ul do
_li do
_a 'New Members Elected By Majority Yes/No/Abstain vote', href: 'https://www.apache.org/foundation/governance/meetings#how-member-votes-are-tallied'
end
_li do
_a 'Board Seats Are Elected By STV - ORDER OF YOUR VOTE MATTERS!', href: 'https://www.apache.org/foundation/governance/meetings#how-votes-for-the-board-are-tallied'
end
end
end
end
_div id: 'secondhalf'
_whimsy_panel("Timeline: Meeting (at #{ics_date.strftime(DTFORMAT)})", style: 'panel-primary') do
_p do
_a href: "http://www.timeanddate.com/worldclock/fixedtime.html?iso=#{ics_date.strftime(TADFORMAT)}" do
_span.glyphicon.glyphicon_time ''
_em '(time)'
end
_ 'The meeting 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.'
_ 'Various data files about the meeting (raw-irc-log, board voting tally) will be checked in within a day after the meeting for historical records.'
_ 'Votes for the Omnibus resolution are included in raw-irc-log. We do not currently publish vote results for new member nominees.'
_ul do
['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
end
_div id: 'after'
_whimsy_panel("Timeline: After This Year's Meeting", style: 'panel-default') do
_p do
_ 'Shortly after the second half Meeting ends, '
_a '@TheASF twitter', href: 'https://twitter.com/theasf'
_ ' will formally announce the new board - 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 '
_b '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. Applications must be signed and submitted to the secretary within 30 days of the meeting to be valid.'
end
end
# Most/all of these links should already be included above
emit_meeting(cur_mtg_dir, svn_mtg_dir, ics_date, 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(ASF::MeetingUtil::RECORDS, File.basename(mtg))
_a tmp, href: tmp
end
end
end
end
end
end
end
end