This is an automated email from the ASF dual-hosted git repository.

curcuru 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 03788bee Test alternate method for copypasta for ASFMM
03788bee is described below

commit 03788bee69cde1606c4864d43f0cd31f507a5cf1
Author: Shane Curcuru <a...@shanecurcuru.org>
AuthorDate: Thu Mar 6 09:44:41 2025 -0500

    Test alternate method for copypasta for ASFMM
---
 www/members/proxy_test.cgi | 329 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 329 insertions(+)

diff --git a/www/members/proxy_test.cgi b/www/members/proxy_test.cgi
new file mode 100755
index 00000000..d02b4bbd
--- /dev/null
+++ b/www/members/proxy_test.cgi
@@ -0,0 +1,329 @@
+#!/usr/bin/env ruby
+PAGETITLE = "TEST: 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 'whimsy/asf/meeting-util'
+
+# Emit basic instructions and details on quorum
+def emit_instructions(today, cur_mtg_dir, meeting)
+  meeting_display = meeting.gsub(%r{\A(\d\d\d\d)(\d\d)(\d\d)\z}, "\\1-\\2-\\3")
+  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 Board Chair
+        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_display} is shown below 
for debugging only.
+      }
+  end
+  _p %{
+    This form allows you to assign a proxy for the upcoming
+    Member's Meeting on #{meeting_display}.  Submitting an attendance proxy 
will
+    help us reach quorum at the start of the meeting - the meeting can't 
formally
+    continue without quorum at the start.
+
+    You can still vote and attend the meeting if you want, and you can revoke a
+    proxy at any time.
+  }
+  _p %{
+    If you submit a proxy, you will still be sent ballots by email to your 
personal
+    @apache.org email address one week ahead of the meeting.
+
+    If you won't have internet access for the full week of the meeting, ask
+    for how to assign a proxy for your vote ballots as well.
+  }
+  _p do
+    _ 'Note while the legal proxy form below states your proxy may have your 
voting rights, in practice '
+    _strong 'you will still be emailed your ballots'
+    _ ' unless you explicitly mark a \'*\' in the appropriate place in the '
+    _code 'proxies'
+    _ ' file.  The great majority of proxies assigned are for attendance only; 
not for voting.'
+  end
+  num_members, quorum_need, num_proxies, attend_irc = 
ASF::MeetingUtil.calculate_quorum(cur_mtg_dir)
+  if num_members
+    _p do
+      _ 'Currently, we must have '
+      _span.text_primary attend_irc
+      _ " Members attend the #{meeting_display} 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, disabled)
+  if disabled
+    _h3 'No upcoming meeting'
+    _p 'There is currently no meeting scheduled. Call back later.'
+    return
+  end
+  begin
+    secretary_id = ASF::Committee.officer('secretary').id
+  rescue StandardError
+    secretary_id = ''
+  end
+
+  help, copypasta = ASF::MeetingUtil.is_user_proxied(cur_mtg_dir, $USER)
+  user_is_proxy = help && copypasta
+  _whimsy_panel(user_is_proxy ? "You Are Proxying For Others" : "Select A 
Proxy For Upcoming Meeting", style: 'panel-success') do
+    _div do
+      if help
+        _p help
+        if copypasta
+          _ul.bg_success do
+            copylines = copypasta.join("\n")
+            _pre copylines
+          end
+        end
+      else
+        _p 'The following members have explicitly volunteered to serve as 
proxies; select any one of them, or select any other member that you know will 
proxy for you (or ask!):'
+        _ul do
+          volunteers.each do |vol|
+            _pre vol
+          end
+        end
+      end
+    end
+
+    if user_is_proxy
+      _p.text_warning %{
+          NOTE: you are proxying for other members, so you cannot assign
+          someone else to proxy for your attendance.  If it turns out that
+          you will not be able to attend the IRC meeting on Thursday,
+          you MUST work with the Board Chair and your proxies to update the
+          proxy records, and get someone else to mark their presence!
+        }
+    else
+      _div.well.well_lg do
+        _form method: 'POST', onsubmit: 'return validateForm();' do
+          _div.form_group do
+            _label 'Select proxy'
+            _b do
+              _p %{
+                WARNING: If you select someone other than the Chair or 
Secretary (*), please note
+                that your proxy will not be counted if the person is unable to 
attend.              }
+            end
+            _p %{
+              (* The meeting will be postponed if the Chair and/or Secretary 
cannot attend)
+            }
+
+
+            # 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
+              if meeting != '20220615'
+                _option 'Select an ASF Member', :selected, value: ''
+              end
+              # Allow for missing public name (should not happen unless LDAP 
is inconsistent)
+              ldap_members.sort_by{|m| m.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 || '?No public name?'} 
(#{member.id})",
+                  selected: (member.id == secretary_id)
+              end
+            end
+          end
+          _div_.form_group do
+            _p do
+              _b 'Note that you cannot select a member who has nominated a 
proxy'
+            end
+            _p do
+              _ "IMPORTANT! Be sure to tell the person that you select as 
proxy above 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
+            _div.button_group.text_center do
+              _button.btn.btn_primary 'Submit'
+            end
+          end
+        end
+        _pre IO.read(File.join(cur_mtg_dir, 'member_proxy.txt'))
+      end
+    end
+  end
+
+  _script src: "js/bootstrap-combobox.js" # TODO do we need this still?
+
+  _script_ %{
+    function validateForm() {
+      if ($('.combobox').val() == '')  {
+        alert("A proxy name is required");
+        return false;
+      }
+      return true;
+    }
+
+    // convert select into combobox
+    $('.combobox').combobox();
+
+    // disable submit until a value is selected
+    if ($('.combobox').val() == '') $('.btn').prop('disabled', true);
+
+    // enable submit when proxy is chosen
+    $('*[name="proxy"]').change(function() {
+      $('.btn').prop('disabled', false);
+      });
+  }
+end
+
+# Emit a record of a user's submission - POST
+def emit_post(cur_mtg_dir, meeting, _)
+  # Detect missing/invalid proxy info (should not happen)
+  raise ArgumentError,"Invalid proxy name '#{@proxy}'" unless @proxy =~ 
%r{\A.+ \([a-z0-9-]+\)\z}
+
+  _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
+
+  # report on commit
+  _div.transcript do
+    Dir.mktmpdir do |tmpdir|
+      svn =  ASF::SVN.getInfoItem(File.join(MEETINGS,meeting),'url')
+
+      ASF::SVN.svn_('checkout',[svn, tmpdir], _,
+                    {quiet: true, user: $USER, password: $PASSWORD})
+      Dir.chdir(tmpdir) do
+        # write proxy form
+        filename = "proxies-received/#{$USER}.txt"
+        update_existing_form = File.exist? filename
+        File.write(filename, proxyform)
+        unless update_existing_form
+          ASF::SVN.svn_('add', filename, _)
+          ASF::SVN.svn_('propset', ['svn:mime-type', 'text/plain; 
charset=utf-8', filename], _)
+        end
+
+        # get a list of proxies
+        list = Dir['proxies-received/*.txt'].map do |file|
+          form = File.read(file)
+
+          id = File.basename(file, '.txt') # assume filename is a valid id
+          proxy = form[/hereby authorize ([\S].*) to act/, 1].
+            gsub('_', ' ').strip
+          # Ensure availid is not included in proxy name here
+          proxy.sub!(%r{\([^)]+\)}, '')
+          proxy.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')
+        # look for lines containing '(id)' which start with 3 spaces
+        # TODO this assumes that the volunteer lines start with 2 spaces
+        existing = proxies.scan(/   \S.*\(\S+\).*$/)
+        # extract the ids
+        existing_ids = existing.map {|line| line[/\((\S+)\)/, 1] }
+        # ensure this id is not treated as previously existing
+        if existing_ids.delete(user.id)
+          existing.reject! {|line| line[/\((\S+)\)$/, 1] == user.id}
+        end
+        # keep only new ids
+        added = list.
+          reject {|line| existing_ids.include? line[/\((\S+)\)$/, 1]}
+        list = added + existing
+        # look for the last '-' at the end of a line.
+        # This should be under the 'For:' column heading just before the 
proxies
+        # TODO it would be safer to look for <name>
+        proxies[/.*-\n(.*)/m, 1] = list.flatten.sort.join("\n") + "\n"
+
+        IO.write('proxies', proxies)
+
+        # commit
+        ASF::SVN.svn_('commit',[filename, 'proxies'], _,
+          {msg: "assign #{@proxy} as my proxy", user: $USER, password: 
$PASSWORD})
+# TODO: send email to @proxy per WHIMSY-78
+      end
+    end
+  end
+
+  # Report on contents now that they're checked in
+  _h3! do
+    _span "Contents of "
+    _code "foundation/Meetings/#{meeting}/#{$USER}.txt"
+    _span " as now checked in to svn:"
+  end
+  _pre proxyform
+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 = ASF::MeetingUtil.get_latest(MEETINGS)
+    meeting = File.basename(cur_mtg_dir)
+    today = Date.today.strftime('%Y%m%d')
+    _whimsy_body(
+      title: PAGETITLE,
+      style: (today > meeting ? 'panel-danger' : 'panel-info'),
+      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, 
ASF::MeetingUtil::getVolunteers(cur_mtg_dir), today > meeting)
+      else # POST
+        # WHIMSY-409: improve UI
+        begin
+          emit_post(cur_mtg_dir, meeting, _)
+        rescue ArgumentError => e
+          _h2_.text_danger {_span.label.label_danger e}
+        end
+      end
+    end
+  end
+end

Reply via email to