| #!/usr/bin/env ruby |
| PAGETITLE = "Member Meeting Proxy Selection Form" # Wvisible:meeting |
| $LOAD_PATH.unshift '/srv/whimsy/lib' |
| |
| require 'whimsy/asf' |
| require 'wunderbar' |
| require 'wunderbar/bootstrap' |
| require 'wunderbar/jquery' |
| require 'date' |
| require 'tmpdir' |
| require_relative 'meeting-util' |
| |
| # TODO: Read in proxies between Volunteers: and Assignments: lines |
| volunteers = [ |
| "Shane Curcuru (curcuru)", |
| "Craig L Russell (clr)" |
| ] |
| |
| # Emit basic instructions and details on quorum |
| def emit_instructions(today, cur_mtg_dir, meeting) |
| if today > meeting |
| _p.text_warning %{ |
| WARNING: Data for the next Member's Meeting is not yet available, |
| so this form will not work yet. Please wait until the Chairman |
| announces the opening of nominations for the board and new members, |
| and then check back to assign a new proxy for the meeting. |
| Data from the previous meeting on #{meeting} is shown below for debugging only. |
| } |
| end |
| _p %{ |
| The bottom of this form allows you to assign an attendance proxy for the upcoming |
| Member's Meeting on #{meeting}. If there is any chance you might not be able |
| to attend the first part of the Member's Meeting on Tuesday, then |
| please assign a proxy, because that helps the meeting reach |
| quorum more quickly. |
| You can still attend the meeting if you want, and can revoke a |
| proxy at any time. |
| } |
| _p %{ |
| If you submit a proxy, you will still be sent board and new member ballots by email |
| during the meeting's 46 hour recess (between Tuesday and Thursday, |
| with two hours for vote counting), so you will still need to |
| cast your votes by checking your mail during the recess. If |
| you won't have internet access the week of the meeting, ask |
| for how to assign a proxy for your vote ballots as well. |
| } |
| num_members, quorum_need, num_proxies, attend_irc = MeetingUtil.calculate_quorum(cur_mtg_dir) |
| if num_members |
| _p do |
| _ 'Currently, we must have ' |
| _span.text_primary "#{attend_irc}" |
| _ " Members attend the first half of the #{meeting} meeting 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 |
| end |
| end |
| |
| # Emit meeting data and form for user to select a proxy - GET |
| def emit_form(cur_mtg_dir, meeting, volunteers) |
| help, copypasta = MeetingUtil.is_user_proxied(cur_mtg_dir, $USER) |
| _whimsy_panel("Select A Proxy For Upcoming Meeting", style: 'panel-success') do |
| _div.row do |
| _div do |
| if help |
| _p help |
| if copypasta |
| _ul.bg_success do |
| copypasta.each do |copyline| |
| _pre copyline |
| end |
| end |
| end |
| else |
| _p 'The following members have volunteered to serve as proxies; you can freely select any one of them below:' |
| _ul do |
| volunteers.each do |vol| |
| _pre vol |
| end |
| end |
| end |
| _p do |
| _ "IMPORTANT! Be sure to tell the person that you select as proxy that you've assigned them to mark your attendance! They simply need to mark your proxy attendance when the meeting starts." |
| _a 'Read full procedures for Member Meeting', href: 'https://www.apache.org/foundation/governance/members.html#meetings' |
| end |
| end |
| |
| _div.row do |
| _div do |
| _pre IO.read(File.join(cur_mtg_dir, 'member_proxy.txt').untaint) |
| end |
| end |
| |
| _form method: 'POST' do |
| _div_.row do |
| _div.form_group do |
| _label 'Select proxy' |
| |
| # Fetch LDAP |
| ldap_members = ASF.members |
| ASF::Person.preload('cn', ldap_members) |
| |
| # Fetch members.txt |
| members_txt = ASF::Member.list |
| |
| # get a list of members who have submitted proxies |
| exclude = Dir[File.join(cur_mtg_dir,'proxies-received', '*')]. |
| map {|name| name[/(\w+)\.\w+$/, 1]} |
| |
| _select.combobox.input_large.form_control name: 'proxy' do |
| _option 'Select an ASF Member', :selected, value: '' |
| ldap_members.sort_by(&:public_name).each do |member| |
| next if member.id == $USER # No self proxies |
| next if exclude.include? member.id # Not attending |
| next unless members_txt[member.id] # Non-members |
| next if members_txt[member.id]['status'] # Emeritus/Deceased |
| # Display the availid to users to match volunteers array above |
| _option "#{member.public_name} (#{member.id})" |
| end |
| end |
| end |
| end |
| |
| _div_.row do |
| _div.button_group.text_center do |
| _button.btn.btn_primary 'Submit' |
| end |
| end |
| end |
| end |
| |
| ## _script src: "js/jquery-1.11.1.min.js" |
| ## _script src: "js/bootstrap.min.js" |
| _script src: "js/bootstrap-combobox.js" # TODO do we need this still? |
| |
| _script_ %{ |
| // convert select into combobox |
| $('.combobox').combobox(); |
| |
| // initially disable submit |
| $('.btn').prop('disabled', true); |
| |
| // enable submit when proxy is chosen |
| $('*[name="proxy"]').change(function() { |
| $('.btn').prop('disabled', false); |
| }); |
| } |
| end |
| end |
| |
| # Emit a record of a user's submission - POST |
| def emit_post(cur_mtg_dir, meeting) |
| _h3_ 'Proxy Assignment - Session Transcript' |
| |
| # collect data |
| proxy = File.read(File.join(cur_mtg_dir, 'member_proxy.txt')) |
| user = ASF::Person.find($USER) |
| date = Date.today.strftime("%B %-d, %Y") |
| |
| # update proxy form (match as many _ as possible up to the name length) |
| proxy[/authorize _(_{,#{@proxy.length}})/, 1] = @proxy.gsub(' ', '_') |
| |
| proxy[/signature: _(_#{'_' *user.public_name.length}_)/, 1] = |
| "/#{user.public_name.gsub(' ', '_')}/" |
| |
| proxy[/name: _(#{'_' *user.public_name.length})/, 1] = |
| user.public_name.gsub(' ', '_') |
| |
| proxy[/availid: _(#{'_' *user.id.length})/, 1] = |
| user.id.gsub(' ', '_') |
| |
| proxy[/Date: _(#{'_' *date.length})/, 1] = date.gsub(' ', '_') |
| |
| proxyform = proxy.untaint |
| |
| # report on commit |
| _div.transcript do |
| Dir.mktmpdir do |tmpdir| |
| svn = `svn info #{MEETINGS}/#{meeting}`[/URL: (.*)/, 1] |
| |
| _.system [ |
| 'svn', 'checkout', '--quiet', svn.untaint, tmpdir.untaint, |
| ['--no-auth-cache', '--non-interactive'], |
| (['--username', $USER, '--password', $PASSWORD] if $PASSWORD) |
| ] |
| |
| Dir.chdir(tmpdir) do |
| # write proxy form |
| filename = "proxies-received/#$USER.txt".untaint |
| File.write(filename, proxyform) |
| _.system ['svn', 'add', filename] |
| _.system ['svn', 'propset', 'svn:mime-type', |
| 'text/plain; charset=utf-8', filename] |
| |
| # get a list of proxies |
| list = Dir['proxies-received/*.txt'].map do |file| |
| form = File.read(file.untaint) |
| |
| id = file[/([-A-Za-z0-9]+)\.\w+$/, 1] |
| proxy = form[/hereby authorize ([\S].*) to act/, 1]. |
| gsub('_', ' ').strip |
| # Ensure availid is not included in proxy name here |
| proxy = proxy[/([^(]+)/, 1].strip |
| name = form[/signature: ([\S].*)/, 1].gsub(/[\/_]/, ' ').strip |
| |
| " #{proxy.ljust(24)} #{name} (#{id})" |
| end |
| |
| # gather a list of all non-text proxies (TODO unused) |
| nontext = Dir['proxies-received/*']. |
| reject {|file| file.end_with? '.txt'}. |
| map {|file| file[/([-A-Za-z0-9]+)\.\w+$/, 1]} |
| |
| # update proxies file |
| proxies = IO.read('proxies') |
| existing = proxies.scan(/ \S.*\(\S+\).*$/) |
| existing_ids = existing.map {|line| line[/\((\S+)\)/, 1] } |
| added = list. |
| reject {|line| existing_ids.include? line[/\((\S+)\)$/, 1]} |
| list = added + existing |
| proxies[/.*-\n(.*)/m, 1] = list.flatten.sort.join("\n") + "\n" |
| |
| IO.write('proxies', proxies) |
| |
| # commit |
| _.system [ |
| 'svn', 'commit', filename, 'proxies', |
| '-m', "assign #{@proxy} as my proxy", |
| ['--no-auth-cache', '--non-interactive'], |
| (['--username', $USER, '--password', $PASSWORD] if $PASSWORD) |
| ] |
| # TODO: send email to @proxy per WHIMSY-78 |
| end |
| end |
| |
| # report on contents |
| _h3! do |
| _span "Contents of " |
| _code "foundation/meetings/#{meeting}/#{$USER}.txt" |
| _span ":" |
| end |
| |
| _pre proxyform |
| end |
| end |
| |
| # produce HTML |
| _html do |
| _style :system |
| _style %{ |
| .transcript {margin: 0 16px} |
| .transcript pre {border: none; line-height: 0} |
| } |
| _body? do |
| # Find latest meeting and check if it's in the future yet |
| MEETINGS = ASF::SVN['Meetings'] |
| cur_mtg_dir = MeetingUtil.get_latest(MEETINGS).untaint |
| meeting = File.basename(cur_mtg_dir) |
| today = Date.today.strftime('%Y%m%d') |
| _whimsy_body( |
| title: PAGETITLE, |
| subtitle: today > meeting ? "ERROR: Next Meeting Data Not Available" : "How To Assign A Proxy For Upcoming Meeting", |
| related: { |
| '/members/meeting' => 'How-To / FAQ for Member Meetings', |
| '/members/attendance-xcheck' => 'Members Meeting Attendance Crosscheck', |
| '/members/inactive' => 'Inactive Member Feedback Form', |
| '/members/subscriptions' => 'Members@ Mailing List Crosscheck' |
| }, |
| helpblock: -> { |
| emit_instructions(today, cur_mtg_dir, meeting) |
| } |
| ) do |
| if _.get? |
| emit_form(cur_mtg_dir, meeting, volunteers) |
| else # POST |
| emit_post(cur_mtg_dir, meeting) |
| end |
| end |
| end |
| end |
| |