www/roster/models/committee.rb (187 lines of code) (raw):
require 'whimsy/asf/mlist'
class Committee
def self.serialize(id, env)
pmc = ASF::Committee.find(id)
return unless pmc.pmc? # Only show PMCs
owners = pmc.owners
committers = pmc.committers
# These may be empty if there is no matching LDAP group, e.g. during initial setup
ASF::Committee.load_committee_info
# We'll be needing the mail data later
ASF::Person.preload(['cn', 'mail', 'asf-altEmail', 'githubUsername'], (owners + committers).uniq)
comdev = ASF::SVN['comdev-foundation']
info = JSON.parse(File.read(File.join(comdev, 'projects.json')))[id]
image = ASF::SiteImage.find(id)
# always needed: if not a member, for checking moderator status
# and if a member, needed for showing list moderators
# will be dropped later if insufficient karma
moderators, modtime = ASF::MLIST.list_moderators(pmc.mail_list)
subscribers = nil # we get the counts only here
subtime = nil
pSubs = [] # private@ subscribers
unMatchedSubs = [] # unknown private@ subscribers
unMatchedSecSubs = [] # unknown security@ subscribers
currentUser = ASF::Person.find(env.user)
# Users have extra karma if they are either of the following:
# ASF member or private@ list moderator (analysePrivateSubs)
# PMC member (isPMCMember)
# These attributes grant access as follows:
# both: can see (*) markers (PPMC members not subscribed to the private@ list)
# both: can see moderator addresses for mailing lists
# analysePrivateSubs: can see crosscheck of private@ list subscriptions
analysePrivateSubs = currentUser.asf_member?
unless analysePrivateSubs # not an ASF member - are we a moderator?
# TODO match using canonical emails
user_mail = currentUser.all_mail || []
pMods = moderators[pmc.private_mail_list] || []
analysePrivateSubs = !(pMods & user_mail).empty?
end
isPMCMember = false # default to not needed
unless analysePrivateSubs
isPMCMember = pmc.roster.include? env.user
end
# Now get the data we are allowed to see
if analysePrivateSubs or isPMCMember
subscribers, subtime = ASF::MLIST.list_subs(pmc.mail_list) # counts only, no archivers
pSubs = ASF::MLIST.private_subscribers(pmc.mail_list)[0]||[]
unMatchedSubs=Set.new(pSubs) if analysePrivateSubs # init ready to remove matched mails
pSubs.map!(&:downcase) # for matching
if analysePrivateSubs
sSubs = ASF::MLIST.security_subscribers(pmc.mail_list)[0]||[]
unMatchedSecSubs=Set.new(sSubs) # init ready to remove matched mails
end
lists = ASF::MLIST.domain_lists(pmc.mail_list, true)
else
lists = ASF::MLIST.domain_lists(pmc.mail_list, false)
end
roster = ASF.dup(pmc.roster) # from committee-info
# ensure PMC members are all processed even they don't belong to the owner group
roster.each do |key, value|
value[:role] = 'PMC member'
next if pmc.ownerids.include?(key) # skip the rest (expensive) if person is in the owner group
person = ASF::Person[key]
next unless person # in case of missing entry (e.g. renamed uid)
if analysePrivateSubs or isPMCMember
# Analyse the subscriptions, matching against canonicalised personal emails
allMail = person.all_mail.map{|m| ASF::Mail.to_canonical(m.downcase)}
# pSubs is already downcased
# TODO should it be canonicalised as well above?
roster[key]['notSubbed'] = true if (allMail & pSubs.map{|m| ASF::Mail.to_canonical(m)}).empty?
end
if analysePrivateSubs
unMatchedSubs.delete_if {|k| allMail.include? ASF::Mail.to_canonical(k.downcase)}
unMatchedSecSubs.delete_if {|k| allMail.include? ASF::Mail.to_canonical(k.downcase)}
end
roster[key]['githubUsername'] = (person.attrs['githubUsername'] || []).join(', ')
end
owners.each do |person| # process the owners
roster[person.id] ||= {
name: person.public_name,
role: 'PMC member' # TODO not strictly true, as CI is the canonical source
}
if analysePrivateSubs or isPMCMember
# Analyse the subscriptions, matching against canonicalised personal emails
allMail = person.all_mail.map{|m| ASF::Mail.to_canonical(m.downcase)}
# pSubs is already downcased
# TODO should it be canonicalised as well above?
roster[person.id]['notSubbed'] = true if (allMail & pSubs.map{|m| ASF::Mail.to_canonical(m)}).empty?
end
if analysePrivateSubs
unMatchedSubs.delete_if {|k| allMail.include? ASF::Mail.to_canonical(k.downcase)}
unMatchedSecSubs.delete_if {|k| allMail.include? ASF::Mail.to_canonical(k.downcase)}
end
roster[person.id]['ldap'] = true
roster[person.id]['githubUsername'] = (person.attrs['githubUsername'] || []).join(', ')
end
committers.each do |person|
roster[person.id] ||= {
name: person.public_name,
role: 'Committer'
}
roster[person.id]['githubUsername'] = (person.attrs['githubUsername'] || []).join(', ')
end
roster.each {|k, v| v[:member] = ASF::Person.find(k).asf_member?}
if pmc.chair and roster[pmc.chair.id]
roster[pmc.chair.id]['role'] = 'PMC chair'
end
# separate out the known ASF members and extract any matching committer details
unknownSubs = [] # unknown private@ subscribers: not PMC or ASF
asfMembers = []
unknownSecSubs = [] # unknown security@ subscribers: not PMC or ASF
# Also look for non-ASF mod emails
nonASFmails = {}
moderators&.each { |_, mods| mods.each {|m| nonASFmails[m] = '' unless m.end_with? '@apache.org'} }
if unMatchedSubs.length > 0 or nonASFmails.length > 0 or unMatchedSecSubs.length > 0
load_emails # set up @people
unMatchedSubs.each { |addr|
who = nil
@people.each do |person|
if person[:mail].any? {|mail| mail.downcase == addr.downcase}
who = person
end
end
if who
if who[:member]
asfMembers << { addr: addr, person: who }
else
unknownSubs << { addr: addr, person: who }
end
else
unknownSubs << { addr: addr, person: nil }
end
}
nonASFmails.each {|k, _|
@people.each do |person|
if person[:mail].any? {|mail| ASF::Mail.to_canonical(mail.downcase) == ASF::Mail.to_canonical(k.downcase)}
nonASFmails[k] = person[:id]
end
end
}
unMatchedSecSubs.each { |addr|
who = nil
@people.each do |person|
if person[:mail].any? {|mail| mail.downcase == addr.downcase}
who = person
end
end
if who
unless who[:member]
unknownSecSubs << { addr: addr, person: who }
end
else
unknownSecSubs << { addr: addr, person: nil }
end
}
end
pmc_chair = false
if pmc.chair
pmcchairs = ASF::Service.find('pmc-chairs')
pmc_chair = pmcchairs.members.include? pmc.chair
end
# drop detailed information if it was only obtained for PMC members
unless analysePrivateSubs
moderators = modtime = subscribers = subtime = nil
nonASFmails = {}
end
ret = {
id: id,
chair: pmc.chair&.id,
chairname: pmc.chair.public_name,
pmc_chair: pmc_chair,
display_name: pmc.display_name,
description: pmc.description,
schedule: pmc.schedule,
report: pmc.report,
site: pmc.site,
established: pmc.established,
ldap: owners.map(&:id), # ldap project owners
members: pmc.roster.keys, # committee-info members
committers: committers.map(&:id),
roster: roster,
mail: lists.sort.to_h,
moderators: moderators,
modtime: modtime,
subscribers: subscribers,
subtime: subtime,
nonASFmails: nonASFmails,
project_info: info,
image: image,
analysePrivateSubs: analysePrivateSubs,
unknownSubs: unknownSubs,
asfMembers: asfMembers,
unknownSecSubs: unknownSecSubs,
}
# Don't add unnecessary settings
ret[:isPMCMember] = isPMCMember if isPMCMember
return ret
end
private
def self.load_emails
# recompute index if the data is 5 minutes old or older
@people = nil if not @people_time or Time.now-@people_time >= 300
unless @people
# bulk loading the mail information makes things go faster
mail = ASF::Mail.list.group_by(&:last).transform_values {|list| list.map(&:first)}
# build a list of people, their public-names, and email addresses
@people = ASF::Person.list.map {|person|
result = {id: person.id, name: person.public_name, mail: mail[person]}
result[:member] = true if person.asf_member?
result
}
# cache
@people_time = Time.now
end
@people
end
end