www/roster/public_committee_info.rb (130 lines of code) (raw):

#{ # Creates JSON output from committee-info.txt with the following format: # "last_updated": "2016-03-04 04:50:00 UTC", # "committee_count": 210, # "roster_counts": { # "accumulo": 40, # ... # }, # "committees": { # "abdera": { # "display_name": "Abdera", # "site": "http://abdera.apache.org/", # "description": "Atom Publishing Protocol Implementation", # "mail_list": "abdera", # "established": "11/2008", # "report": [ # "Next month: missing in February", # "February", # "May", # "August", # "November" # ], # "chair": { # "availid": { # "name": "Some One" # } # }, # "roster": { # "availid": { # "name": "Some One", # "date": "2009-10-21" # }, # ... # }, # "pmc": true # }, # If a second parameter is provided, also creates committee-retired info of the form: #{ # "last_updated": "2016-03-04 04:50:00 UTC", # "retired_count": 123, # "retired": { # "abdera": { # "display_name": "Abdera", # "description": "Atom Publishing Protocol Implementation", # "retired": "yyyy-mm". # "mlists": ["dev", "user", ...] // lists not yet shut down # }, require_relative 'public_json_common' require 'whimsy/asf/board' # gather committee info committees = ASF::Committee.load_committee_info # reformat the data info = { last_updated: ASF::Committee.svn_change, committee_count: committees.size, pmc_count: 0, roster_counts: nil } roster_counts = {} info[:committees] = committees.map {|committee| schedule = committee.schedule.to_s.split(/,\s*/) schedule.unshift committee.report if committee.report != committee.schedule cname = committee.name.gsub(/[^-\w]/, '') data = { display_name: committee.display_name, site: committee.site, description: committee.description, mail_list: committee.mail_list, established: committee.established, report: schedule, # Convert {:name=>"Public Name", :id=>"availid"} to # "chair": { "availid": { "name": "Public Name" } } chair: committee.chairs.map {|chair| [chair[:id], {:name => chair[:name]}]}.to_h, roster_count: committee.roster.size, roster: committee.roster.sort.to_h, # sort entries by uid pmc: committee.pmc? } info[:pmc_count] += 1 if committee.pmc? roster_counts[cname] = committee.roster.size data[:paragraph] = committee.paragraph if committee.paragraph [cname, data] }.to_h info[:roster_counts] = roster_counts info[:officers] = ASF::Committee.officers.map { |officer| [officer.name.gsub(/[^-\w]/, ''), { display_name: officer.display_name, paragraph: officer.paragraph, # will always be present roster: officer.chairs.map {|e| [e[:id], {:name => e[:name]}]}.to_h } ] }.to_h info[:board] = { roster: ASF::Board.directors(true) } public_json_output(info) # Check if there is an unexpected entry date # we only do this if the file has changed to avoid excessive reports if check_now? # Note: symbolize_names=false to avoid symbolising variable keys such as pmc and user names # However the current JSON (info) uses symbols for fixed keys - beware! previous = JSON.parse(@old_file, :symbolize_names => false) previous = previous['committees'] last_updated = info[:last_updated] # This is a Time instance # the joining date should normally be the same as the date when the file was updated: updated_day1 = last_updated.strftime('%Y-%m-%d') # day of update # and the date must be after the last time the data was checked. # Unfortunately the last_updated field is only updated when the content changes - # there is currently no record of when the last check was done. # For now, just assume that this is done every 15 mins. This may cause spurious reports # if the checks are ever suspended for longer and meanwhile changes occur. # Note: for those in an earlier timezone the date could be a few hours earlier updated_day2 = (last_updated - 3600 * 4).strftime('%Y-%m-%d') # day of previous update # for validating UIDs uids = ASF::Person.list().map(&:id) info[:committees].each { |pmc, entry| next if pmc == 'infrastructure' # no dates Wunderbar.warn "#{pmc}: no description found" if entry[:pmc] && !entry[:description] previouspmc = previous[pmc] # get the original details (if any) if previouspmc # we have an existing entry entry[:roster].each { |name, value| newdate = value[:date] if newdate.nil? Wunderbar.warn "Un-dated member for #{pmc}: #{name} #{value[:name]} #{newdate}" next end if previouspmc['roster'][name].nil? # new name, check the date is OK if newdate <= updated_day1 and newdate >= updated_day2 # in range Wunderbar.info "New member for #{pmc}: #{name} #{value[:name]} #{newdate}" elsif newdate > updated_day1 Wunderbar.warn "Future-dated member for #{pmc}: #{name} #{value[:name]} #{newdate}" else Wunderbar.warn "Past-dated member for #{pmc}: #{name} #{value[:name]} #{newdate}" end else olddate = previouspmc['roster'][name]['date'] if olddate != newdate Wunderbar.warn "Changed date member for #{pmc}: #{name} #{value[:name]} #{olddate} => #{newdate}" end end } else Wunderbar.info "New PMC detected: #{pmc}" # Could check that the joining dates are all the same? end entry[:roster].each { |id, _| Wunderbar.warn "#{pmc}: unknown uid '#{id}'" unless uids.include?(id) } } previous.each { |pmc, _| unless info[:committees][pmc] Wunderbar.info "Deleted PMC detected: #{pmc}" end } end # do we awant attic info? if ARGV.length >= 2 ARGV.shift # we have already used this require 'whimsy/asf/mlist' metadata = ASF::Committee.load_committee_metadata[:tlps] public_lists = Hash.new {|h, k| h[k] = Array.new} begin ASF::MLIST.list_types(true) do |dom, list, _| public_lists[dom.sub(/\.apache\.org$/, '')] << list end rescue StandardError => e Wunderbar.error e.inspect end # reformat the data attic = {last_updated: ASF::Committee.meta_change} data = {} active = info[:committees].keys metadata.each do |key, value| retired = value[:retired] if retired Wunderbar.warn "#{key} has no display name" unless value[:name] data[key] = { display_name: value[:name], retired: retired } data[key][:description] = value[:description] if value[:description] mlists = public_lists[key] if mlists.size > 0 data[key][:mlists] = mlists end else unless active.include? key Wunderbar.warn "Checking committee-info.yml: has '#{key}'' retired? Could not find it in committee-info.txt!" end end end attic[:retired_count] = data.size attic[:retired] = data public_json_output(attic) # uses first ARGV entry end