www/members/watch.cgi (299 lines of code) (raw):

#!/usr/bin/env ruby PAGETITLE = "Potential ASF Member Watch List" # Wvisible:members $LOAD_PATH.unshift '/srv/whimsy/lib' require 'wunderbar' require 'whimsy/asf' require 'whimsy/asf/member-files' require 'nokogiri' require 'date' require 'wunderbar/bootstrap' require 'wunderbar/jquery/stupidtable' _html do _head_ do _base href: File.dirname(ENV['SCRIPT_NAME']) end _body? do _whimsy_body( title: PAGETITLE, related: { '/members/memberless-pmcs.cgi' => 'PMCs with no/few ASF Members', '/members/nominate_member.cgi' => 'Nominate someone for ASF Member', '/members/check_membernoms.cgi' => 'Cross-check existing New Member Nominations', ASF::SVN.svnpath!('Meetings') => 'Official Meeting Agenda Directory' }, helpblock: -> { _ 'To help evaluate potential Member candidates, here are a number of ways to see where non-Members are participating broadly at the ASF.' _ 'The table(s) below include non-Members who are chairs, widely active, have been nominated, or other criteria (depending on this URL).' } ) do # start with the Watch List itself watch_list = ASF::Person.member_watch_list.keys # array of Person entries meeting = ASF::MemberFiles.latest_meeting nominations = ASF::MemberFiles.member_nominees # determine which list to report on, based on the URI request = ENV['REQUEST_URI'] _div.row do _div.col_sm10 do _div.panel.panel_primary do _div.panel_heading {_h3.panel_title 'Related Links'} _div.panel_body do _ul do if Time.new.strftime('%Y%m%d') < File.basename(meeting) _li do _a 'Cross-check existing New Member nominations', href: '/members/check_membernoms.cgi' end else unless request =~ /appstatus/ _li do _a 'Application Status', href: 'members/watch/appstatus' end end end if request =~ %r{/watch/\w} # only show main link for a sub-page _li do _a 'Potential Member Watch List', href: 'members/watch' end end unless request =~ /nominees/ _li do _a 'Nominees', href: 'members/watch/nominees' end end unless request =~ /multiple/ _li do _a 'Active in Multiple (>=3) PMCs', href: 'members/watch/multiple' end end unless request =~ /chairs/ _li do _a 'Non-member PMC chairs', href: 'members/watch/chairs' end end unless request =~ /mentors/ _li do _a 'Non-member incubator mentors', href: 'members/watch/mentors' end end _li do _a 'PMCs with no/few members', href: 'members/memberless-pmcs' end end end end end end list = {} # Avoid lint errors of shadowing if request =~ /multiple/ _h2_ 'Active In Multiple Committees' # Use actual PMCs rather than LDAP derived list = ASF::Committee.pmcs.map {|pmc| pmc.roster.keys}. reduce(&:+).group_by {|uid| uid}. delete_if {|_, lst| lst.length < 3}. map {|uid, _| ASF::Person.find(uid)} list -= ASF.members elsif request =~ /chairs/ _h2_ 'PMC Chairs' list = ASF.pmc_chairs list -= ASF.members elsif request =~ /mentors/ _h2_ 'Incubator Mentors' list = ASF::Podling.current.map(&:mentors).flatten. uniq.map {|id| ASF::Person.find(id)} list -= ASF.members elsif request =~ /nominees/ _h2_ 'Member Nominees' # Keep only ids that are not current members list = nominations.filter_map do |id, info| if id.start_with? 'n/a_' p = ASF::Person.new(id) p.attrs['cn'] = info['Public Name'] # public name p else p = ASF::Person.find(id) p if p.asf_member_status != :current # drop current members end end elsif request =~ /appstatus/ _h2_ 'Elected Members - Application Status' nanum = 0 status = {} # status keyed by id or notinavail_n list = [] # list of People entries File.read(File.join(meeting, 'memapp-received.txt')). scan(/^(yes|no)\s+(yes|no)\s+(yes|no)\s+(yes|no)\s+(\S+)\s+(\S.+)/).each do |tokens| name = tokens.pop id = tokens.pop if id == 'n/a' nanum += 1 id = "notinavail_#{nanum+=1}" p = ASF::Person.new(id) p.attrs['cn'] = name # public name list << p else list << ASF::Person.find(id) end status[id] = tokens end _p do applied = status.filter_map {|k, v| list.find{|p| p.name == k}.public_name.to_s if v[1] == 'yes' } _ "Applied: (#{applied.size}) " _ applied.sort.join(', ') end else _h2_ 'From potential-member-watch-list.txt' list = watch_list end _table.table do _thead_ do _tr do if request =~ /appstatus/ _th 'Invited?', data_sort: 'string' _th 'Applied?', data_sort: 'string' _th 'members@?', data_sort: 'string' _th 'Karma', data_sort: 'string' elsif request =~ /nominees/ _th 'Seconded?' else _th 'Nominated?' end _th 'AvailID', data_sort: 'string' _th 'Name', data_sort: 'string' if request !~ /appstatus/ _th 'Committees', data_sort: 'string' _th 'Chair Since', data_sort: 'string' end end end _tbody do list.sort_by {|id| id.public_name.to_s}.each do |person| _tr_ do if request =~ /appstatus/ cols = status[person.id] if cols[0] == 'yes' _td cols[0] else _td.text_danger cols[0] end if cols[0] == 'no' or cols[1] == 'yes' _td cols[1] else _td.text_danger cols[1] end if cols[1] == 'no' or cols[2] == 'yes' _td cols[2] else _td.text_danger cols[2] end if cols[3] == 'yes' _td cols[3], class: ('issue' unless person.asf_member?) elsif cols[1] == 'no' _td cols[3], class: ('issue' if person.asf_member?) else _td.text_danger cols[3] end elsif request =~ /nominees/ if person.member_nomination =~ /Seconded by: \w/ _td 'yes' else _td.text_danger 'no' end else if nominations.key? person.id _td 'yes' else _td end end # ASF id if person.id =~ %r{^(notinavail_|n/a_)\d+$} _td! '(not yet a committer)' elsif person.asf_member_status == :current _td! do _strong {_a person.id, href: "roster/committer/#{person.id}"} end elsif person.asf_member_status == :emeritus _td! do _strong {_a person.id, href: "roster/committer/#{person.id}"} _ ' (emeritus)' end else _td! {_a person.id, href: "roster/committer/#{person.id}"} end # public name _td person.public_name if request !~ /appstatus/ # committees _td do person.committees.sort_by(&:name).each do |committee| if committee.chair == person _strong do _a committee.name, href: "roster/committee/#{committee.name}" end else _a committee.name, href: "roster/committee/#{committee.name}" end end end # chair since chair = person.committees.find {|committee| committee.chair == person} if chair minutes = Dir['../board/minutes/*'].find do |name| File.basename(name).split('.').first.downcase.gsub(/[_\W]/,'') == chair.name.gsub(/\W/,'') end search_string = "RESOLVED, that #{person.public_name}" search_string.force_encoding('utf-8') # search published minutes if minutes resolution = nil Nokogiri::HTML(File.read(minutes)).search('pre').each do |pre| if pre.text.include? search_string resolution = pre while resolution and resolution.name != 'h2' resolution = resolution.previous end break if resolution end end end date = 'unknown' if minutes minutes = 'board/minutes/' + File.basename(minutes) end if resolution minutes += '#' + resolution.at('a')['id'] date = Date.parse(resolution.text) else # search unpublished agendas board = ASF::SVN['foundation_board'] Dir[File.join(board, 'board_agenda_*')].sort.each do |agenda| if File.read(agenda).include? search_string minutes = ASF::SVN.svnpath!('foundation_board', File.basename(agenda)) date = agenda.gsub('_','-')[/(\d+-\d+-\d+)/,1] break end end end _td do _a date, href: minutes end else _td '-' end end end end end end _script %{ var table = $(".table").stupidtable(); table.on("aftertablesort", function (event, data) { var th = $(this).find("th"); th.find(".arrow").remove(); var dir = $.fn.stupidtable.dir; var arrow = data.direction === dir.ASC ? "&uarr;" : "&darr;"; th.eq(data.column).append('<span class="arrow">' + arrow +'</span>'); }); } end end end