This is an automated email from the ASF dual-hosted git repository.
sebb 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 f4211a5 Add new code to display non-PMC committees
f4211a5 is described below
commit f4211a5f28a2aae107d39808aa69fe80490fa32a
Author: Sebb <[email protected]>
AuthorDate: Thu Jan 10 21:22:38 2019 +0000
Add new code to display non-PMC committees
Needs to be wired in later
---
www/roster/models/nonpmc.rb | 200 +++++++++++++++++++++++++++++
www/roster/views/nonpmc.html.rb | 26 ++++
www/roster/views/nonpmc/add.js.rb | 103 +++++++++++++++
www/roster/views/nonpmc/committers.js.rb | 91 ++++++++++++++
www/roster/views/nonpmc/main.js.rb | 208 +++++++++++++++++++++++++++++++
www/roster/views/nonpmc/mod.js.rb | 106 ++++++++++++++++
www/roster/views/nonpmc/nonpmc.js.rb | 167 +++++++++++++++++++++++++
www/roster/views/nonpmcs.html.rb | 63 ++++++++++
8 files changed, 964 insertions(+)
diff --git a/www/roster/models/nonpmc.rb b/www/roster/models/nonpmc.rb
new file mode 100644
index 0000000..f707518
--- /dev/null
+++ b/www/roster/models/nonpmc.rb
@@ -0,0 +1,200 @@
+class NonPMC
+ def self.serialize(id, env)
+ response = {}
+
+ cttee = ASF::Committee.find(id)
+ return unless cttee.nonpmc?
+ members = cttee.owners
+ committers = cttee.committers
+# return if members.empty? and committers.empty?
+
+ ASF::Committee.load_committee_info
+ # We'll be needing the mail data later
+ people = ASF::Person.preload(['cn', 'mail', 'asf-altEmail',
'githubUsername'], (members + committers).uniq)
+
+ lists = ASF::Mail.lists(true).select do |list, mode|
+ list =~ /^#{cttee.mail_list}\b/
+ end
+
+ comdev = ASF::SVN['comdev-foundation']
+ info = JSON.parse(File.read(File.join(comdev, 'projects.json')))[id]
+
+ image_dir = ASF::SVN.find('site-img')
+ image = Dir[File.join(image_dir, "#{id}.*")].map {|path|
File.basename(path)}.last
+
+ moderators = nil
+ modtime = nil
+ subscribers = nil # we get the counts only here
+ subtime = nil
+ pSubs = Array.new # private@ subscribers
+ unMatchedSubs = [] # unknown private@ subscribers
+ unMatchedSecSubs = [] # unknown security@ subscribers
+ currentUser = ASF::Person.find(env.user)
+ analysePrivateSubs = false # whether to show missing private@ subscriptions
+ if cttee.roster.include? env.user or currentUser.asf_member?
+ require 'whimsy/asf/mlist'
+ moderators, modtime = ASF::MLIST.list_moderators(cttee.mail_list)
+ subscribers, subtime = ASF::MLIST.list_subscribers(cttee.mail_list) #
counts only
+ analysePrivateSubs = currentUser.asf_member?
+ unless analysePrivateSubs # check for private moderator if not already
allowed access
+ user_mail = currentUser.all_mail || []
+ pMods = moderators["private@#{cttee.mail_list}.apache.org"] || []
+ analysePrivateSubs = !(pMods & user_mail).empty?
+ end
+ if analysePrivateSubs
+ pSubs = ASF::MLIST.private_subscribers(cttee.mail_list)[0]||[]
+ unMatchedSubs=Set.new(pSubs) # init ready to remove matched mails
+ pSubs.map!{|m| m.downcase} # for matching
+ sSubs = ASF::MLIST.security_subscribers(cttee.mail_list)[0]||[]
+ unMatchedSecSubs=Set.new(sSubs) # init ready to remove matched mails
+ end
+ else
+ lists = lists.select {|list, mode| mode == 'public'}
+ end
+
+ roster = cttee.roster.dup
+ roster.each {|key, info| info[:role] = 'PMC member'}
+
+ members.each do |person|
+ roster[person.id] ||= {
+ name: person.public_name,
+ role: 'PMC member'
+ }
+ if analysePrivateSubs
+ allMail = person.all_mail.map{|m| m.downcase}
+ roster[person.id]['notSubbed'] = (allMail & pSubs).empty?
+ unMatchedSubs.delete_if {|k| allMail.include? k.downcase}
+ unMatchedSecSubs.delete_if {|k| allMail.include? 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 {|id, info| info[:member] = ASF::Person.find(id).asf_member?}
+
+ if cttee.chair and roster[cttee.chair.id]
+ roster[cttee.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=Hash.new
+ if moderators
+ moderators.each { |list,mods| mods.each {|m| nonASFmails[m]='' unless
m.end_with? '@apache.org'} }
+ end
+ 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,v|
+ @people.each do |person|
+ if person[:mail].any? {|mail| mail.downcase == 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]
+ unknownSubs << { addr: addr, person: who }
+ end
+ else
+ unknownSecSubs << { addr: addr, person: nil }
+ end
+ }
+ end
+
+ pmc_chair = false
+ if cttee.chair
+ pmcchairs = ASF::Service.find('cttee-chairs')
+ pmc_chair = pmcchairs.members.include? cttee.chair
+ end
+ response = {
+ id: id,
+ chair: cttee.chair && cttee.chair.id,
+ pmc_chair: pmc_chair,
+ display_name: cttee.display_name,
+ description: cttee.description,
+ schedule: cttee.schedule,
+ report: cttee.report,
+ site: cttee.site,
+ established: cttee.established,
+ ldap: members.map(&:id),
+ members: cttee.roster.keys,
+ committers: committers.map(&:id),
+ roster: roster,
+ mail: Hash[lists.sort],
+ moderators: moderators,
+ modtime: modtime,
+ subscribers: subscribers,
+ subtime: subtime,
+ nonASFmails: nonASFmails,
+ project_info: info,
+ image: image,
+ guinea_pig: ASF::Committee::GUINEAPIGS.include?(id),
+ analysePrivateSubs: analysePrivateSubs,
+ unknownSubs: unknownSubs,
+ asfMembers: asfMembers,
+ unknownSecSubs: unknownSecSubs,
+ }
+
+ response
+ 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
+
+ if not @people
+ # bulk loading the mail information makes things go faster
+ mail = Hash[ASF::Mail.list.group_by(&:last).
+ map {|person, list| [person, 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
diff --git a/www/roster/views/nonpmc.html.rb b/www/roster/views/nonpmc.html.rb
new file mode 100644
index 0000000..5439afa
--- /dev/null
+++ b/www/roster/views/nonpmc.html.rb
@@ -0,0 +1,26 @@
+#
+# A single committee
+#
+
+_html do
+ _base href: '..'
+ _title @nonpmc[:display_name]
+ _link rel: 'stylesheet', href: "stylesheets/app.css?#{cssmtime}"
+
+ _body? do
+ _whimsy_body(
+ breadcrumbs: {
+ roster: '.',
+ nonpmc: 'nonpmc/',
+ @nonpmc[:id] => "nonpmc/#{@nonpmc[:id]}"
+ }
+ ) do
+ _div_.main!
+ end
+
+ _script src: "app.js?#{appmtime}"
+ _.render '#main' do
+ _NonPMC nonpmc: @nonpmc, auth: @auth
+ end
+ end
+end
diff --git a/www/roster/views/nonpmc/add.js.rb
b/www/roster/views/nonpmc/add.js.rb
new file mode 100644
index 0000000..918d73a
--- /dev/null
+++ b/www/roster/views/nonpmc/add.js.rb
@@ -0,0 +1,103 @@
+#
+# Add People to a Committee
+#
+
+class NonPMCAdd < Vue
+ mixin ProjectAdd
+ options add_tag: "pmcadd", add_action: 'actions/nonpmc'
+
+ def initialize
+ @people = []
+ end
+
+ def render
+ _div.modal.fade id: $options.add_tag, tabindex: -1 do
+ _div.modal_dialog do
+ _div.modal_content do
+ _div.modal_header.bg_info do
+ _button.close 'x', data_dismiss: 'modal'
+ _h4.modal_title 'Add People to the ' + @@project.display_name +
+ ' Project'
+ _br
+ _p '***Not currently implemented***'
+ end
+ end
+ end
+ end
+ end
+ def norender
+ _div.modal.fade id: $options.add_tag, tabindex: -1 do
+ _div.modal_dialog do
+ _div.modal_content do
+ _div.modal_header.bg_info do
+ _button.close 'x', data_dismiss: 'modal'
+ _h4.modal_title 'Add People to the ' + @@project.display_name +
+ ' Project'
+ _p {
+ _br
+ _b 'N.B'
+ _br
+ _ 'To add existing committers to the Committee, please cancel
this dialog. Select the committer from the list and use the Modify button.'
+ }
+ end
+
+ _div.modal_body do
+ _div.container_fluid do
+
+ unless @people.empty?
+ _table.table do
+ _thead do
+ _tr do
+ _th 'id'
+ _th 'name'
+ _th 'email'
+ end
+ end
+ _tbody do
+ @people.each do |person|
+ _tr do
+ _td person.id
+ _td person.name
+ _td person.mail[0]
+ end
+ end
+ end
+ end
+ end
+
+ _CommitterSearch add: self.add,
+ exclude: @@project.roster.keys().
+ concat(@people.map {|person| person.id})
+
+ _p do
+ _label do
+ _input type: 'checkbox', checked: @notice_elapsed
+ _a '72 hour board@ NOTICE',
+ href: 'https://www.apache.org/dev/pmc.html#notice_period'
+ _span ' period elapsed?'
+ end
+ end
+ end
+ end
+
+ _div.modal_footer do
+ _span.status 'Processing request...' if @disabled
+
+ _button.btn.btn_default 'Cancel', data_dismiss: 'modal',
+ disabled: @disabled
+
+ plural = (@people.length > 1 ? 's' : '')
+
+ _button.btn.btn_primary "Add as committer#{plural}",
+ data_action: 'add commit',
+ onClick: self.post, disabled: (@people.empty?)
+
+ _button.btn.btn_primary 'Add to Committee', onClick: self.post,
+ data_action: 'add pmc info commit',
+ disabled: (@people.empty? or not @notice_elapsed)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/www/roster/views/nonpmc/committers.js.rb
b/www/roster/views/nonpmc/committers.js.rb
new file mode 100644
index 0000000..13e5513
--- /dev/null
+++ b/www/roster/views/nonpmc/committers.js.rb
@@ -0,0 +1,91 @@
+#
+# Committers on th
+#
+
+class NonPMCCommitters < Vue
+ def render
+ _ __FILE__
+ if
+ @@nonpmc.committers.all? do |id|
+ @@nonpmc.members.include? id
+ end
+ then
+ _h2.committers! 'Committers (' + committers.length + ')'
+ _p 'All committers are members of the Committee'
+ else
+ _h2.committers! do
+ _ 'Committers (' + committers.length + ')'
+ _small ' (the listing excludes Committee members above)'
+ end
+ _p 'Click on column name to sort'
+ _table.table.table_hover do
+ _thead do
+ _tr do
+ _th if @@auth
+ _th 'id', data_sort: 'string'
+ _th 'githubUsername', data_sort: 'string'
+ _th.sorting_asc 'public name', data_sort: 'string-ins'
+ end
+ end
+
+ _tbody do
+ committers.each do |person|
+ next if @@nonpmc.members.include? person.id
+ next if @@nonpmc.ldap.include? person.id
+ _NonPMCCommitter auth: @@auth, person: person, nonpmc: @@nonpmc
+ end
+ end
+ end
+ end
+ end
+
+ def mounted()
+ jQuery('.table', $el).stupidtable()
+ end
+
+ # compute list of committers
+ def committers
+ result = []
+
+ @@nonpmc.committers.each do |id|
+ person = @@nonpmc.roster[id]
+ person.id = id
+ result << person
+ end
+
+ result.sort_by {|person| person.name}
+ end
+end
+
+#
+# Show a committer
+#
+
+class NonPMCCommitter < Vue
+ def render
+ _tr do
+ if @@auth
+ _td do
+ _input type: 'checkbox', checked: @@person.selected || false,
+ onChange: -> {self.toggleSelect(@@person)}
+ end
+ end
+
+ if @@person.member
+ _td { _b { _a @@person.id, href: "committer/#{@@person.id}"} }
+ _td @@person.githubUsername
+ _td { _b @@person.name }
+ else
+ _td { _a @@person.id, href: "committer/#{@@person.id}" }
+ _td @@person.githubUsername
+ _td @@person.name
+ end
+ end
+ end
+
+ # toggle checkbox
+ def toggleSelect(person)
+ person.selected = !person.selected
+ @@nonpmc.refresh()
+ end
+end
diff --git a/www/roster/views/nonpmc/main.js.rb
b/www/roster/views/nonpmc/main.js.rb
new file mode 100644
index 0000000..01919d8
--- /dev/null
+++ b/www/roster/views/nonpmc/main.js.rb
@@ -0,0 +1,208 @@
+#
+# Show a Committee
+#
+
+class NonPMC < Vue
+ def initialize
+ @attic = nil
+ end
+
+ def render
+ if @nonpmc.guinea_pig
+ auth = (@@auth.secretary or @@auth.root or
+ @nonpmc.members.include? @@auth.id)
+ else
+ auth = (@@auth.id == @nonpmc.chair or @@auth.secretary or @@auth.root)
+ end
+
+ auth = nil # The modules have not been checked
+
+ # add jump links to main sections of page using Bootstrap nav element
+ _ul.nav.nav_pills do
+ _li role: "presentation" do
+ _a 'Committee', :href => "nonpmc/#{@nonpmc.id}#pmc"
+ end
+ _li role: "presentation" do
+ _a 'Committers', :href => "nonpmc/#{@nonpmc.id}#committers"
+ end
+ _li role: "presentation" do
+ if @nonpmc.moderators
+ _a 'Mail List Info', :href => "nonpmc/#{@nonpmc.id}#mail"
+ else
+ _a 'Mail Lists', :href => "nonpmc/#{@nonpmc.id}#mail"
+ end
+ end
+ end
+ # header
+ _h1 do
+ _a @nonpmc.display_name, href: @nonpmc.site
+ _small " established #{@nonpmc.established}" if @nonpmc.established
+ if @nonpmc.image
+ _img src: "https://apache.org/img/#{@nonpmc.image}"
+ end
+ end
+
+ _p @nonpmc.description
+
+ # action bar: add, modify, search
+ _div.row key: 'databar' do
+ _div.col_sm_6 do
+ if auth
+ _button.btn.btn_default 'Add',
+ data_target: '#pmcadd', data_toggle: 'modal'
+
+ mod_disabled = true
+ for id in @nonpmc.roster
+ if @nonpmc.roster[id].selected
+ mod_disabled = false
+ break
+ end
+ end
+
+ if mod_disabled
+ _button.btn.btn_default 'Modify', disabled: true
+ else
+ _button.btn.btn_primary 'Modify',
+ data_target: '#pmcmod', data_toggle: 'modal'
+ end
+ _p do
+ _br
+ _ 'Note: to Add existing committers to the Committee, please
select the committer from the list below and use the Modify button instead.'
+ end
+ end
+ end
+ _div.col_sm_6 do
+ _input.form_control type: 'search', placeholder: 'search',
+ value: @search
+ end
+ end
+
+ # main content
+ if @search
+ _ProjectSearch auth: auth, project: @nonpmc, search: @search
+ else
+ _NonPMCMembers auth: auth, nonpmc: @nonpmc
+ _NonPMCCommitters auth: auth, nonpmc: @nonpmc
+ end
+
+ # mailing lists
+ if @nonpmc.moderators
+ _h2.mail! do
+ _ 'Mailing list info'
+ _small ' (subscriber count includes archivers)'
+ end
+ _table do
+ _thead do
+ _tr do
+ _th 'list name'
+ _th do
+ _ 'moderators'
+ _small " (last checked #{@nonpmc.modtime})"
+ end
+ _th do
+ _ 'subscribers'
+ _small " (last checked #{@nonpmc.subtime})"
+ end
+ end
+ end
+ _tbody do
+ for list_name in @nonpmc.moderators
+ _tr do
+ _td do
+ _a list_name, href: 'https://lists.apache.org/list.html?' +
+ list_name
+ end
+ _td do
+ sep=''
+ @nonpmc.moderators[list_name].each { |mod|
+ _ sep
+ id=nil
+ if mod.end_with? '@apache.org'
+ id=mod.sub(/@a.*/,'')
+ else
+ id = @nonpmc.nonASFmails[mod]
+ end
+ if id
+ _a mod, href: "committer/#{id}"
+ else
+ _ mod
+ end
+ sep=', '
+ }
+ end
+ _td @nonpmc.subscribers[list_name]
+ end
+ end
+ end
+ end
+ else
+ _h2.mail! 'Mail lists'
+ _ul do
+ for mail_name in @nonpmc.mail
+ parsed = mail_name.match(/^(.*?)-(.*)/)
+ list_name = "#{parsed[2]}@#{parsed[1]}.apache.org"
+ _li do
+ _a list_name, href: 'https://lists.apache.org/list.html?' +
+ list_name
+ end
+ end
+ end
+ end
+
+ # reporting schedule and links
+ if @nonpmc.report
+ _div.row do
+ _div.col_md_6 do
+ _h3.reporting! 'Reporting Schedule'
+ _ul do
+ _li @nonpmc.report
+ if @nonpmc.schedule and @nonpmc.schedule != @nonpmc.report
+ _li @nonpmc.schedule
+ end
+ _li do
+ _a 'Prior reports', href:
'https://whimsy.apache.org/board/minutes/' +
+ @nonpmc.display_name.gsub(/\s+/, '_')
+ end
+ end
+ end
+ end
+ end
+
+ # hidden forms
+ if auth
+ _Confirm action: :nonpmc, project: @nonpmc.id, update: self.update
+ _NonPMCAdd project: @@nonpmc, onUpdate: self.update
+ _NonPMCMod project: @@nonpmc, onUpdate: self.update
+ end
+ end
+
+ # capture nonpmc on initial load
+ def created()
+ self.update(@@nonpmc)
+ end
+
+ # update nonpmc from conformation form
+ def update(nonpmc)
+ @nonpmc = nonpmc
+
+ @nonpmc.refresh = proc { Vue.forceUpdate() }
+
+ if @attic == nil and not nonpmc.established and defined? fetch
+ @attic = []
+
+ Polyfill.require(%w(Promise fetch)) do
+ fetch('attic/issues.json', credentials: 'include').then {|response|
+ if response.status == 200
+ response.json().then do |json|
+ @attic = json
+ end
+ else
+ console.log "Attic JIRA #{response.status} #{response.statusText}"
+ end
+ }.catch {|error|
+ console.log "Attic JIRA #{error}"
+ }
+ end
+ end
+ end
+end
diff --git a/www/roster/views/nonpmc/mod.js.rb
b/www/roster/views/nonpmc/mod.js.rb
new file mode 100644
index 0000000..2c0182c
--- /dev/null
+++ b/www/roster/views/nonpmc/mod.js.rb
@@ -0,0 +1,106 @@
+#
+# Modify People's role in a project
+#
+
+class NonPMCMod < Vue
+ mixin ProjectMod
+ options mod_tag: "pmcmod", mod_action: 'actions/nonpmc'
+
+ def initialize
+ @people = []
+ end
+
+ def render
+ _div.modal.fade.pmcmod! tabindex: -1 do
+ _div.modal_dialog do
+ _div.modal_content do
+ _div.modal_header.bg_info do
+ _button.close 'x', data_dismiss: 'modal'
+ _h4.modal_title "Modify People's Roles in the " +
+ @@project.display_name + ' Project'
+ _br
+ _p '***Not currently implemented***'
+ end
+ end
+ end
+ end
+ end
+ def norender
+ _div.modal.fade.pmcmod! tabindex: -1 do
+ _div.modal_dialog do
+ _div.modal_content do
+ _div.modal_header.bg_info do
+ _button.close 'x', data_dismiss: 'modal'
+ _h4.modal_title "Modify People's Roles in the " +
+ @@project.display_name + ' Project'
+ end
+
+ _div.modal_body do
+ _div.container_fluid do
+ _table.table do
+ _thead do
+ _tr do
+ _th 'id'
+ _th 'name'
+ end
+ end
+ _tbody do
+ @people.each do |person|
+ _tr do
+ _td person.id
+ _td person.name
+ end
+ end
+ end
+ end
+ end
+
+ # add to Committee button is only shown if every person is not on
the Committee
+ if @people.all? {|person| !@@project.members.include? person.id}
+ _p do
+ _label do
+ _input type: 'checkbox', checked: @notice_elapsed
+ _a '72 hour board@ NOTICE',
+ href: 'https://www.apache.org/dev/pmc.html#notice_period'
+ _span ' period elapsed?'
+ end
+ end
+ end
+ end
+
+ _div.modal_footer do
+ _span.status 'Processing request...' if @disabled
+
+ _button.btn.btn_default 'Cancel', data_dismiss: 'modal',
+ disabled: @disabled
+
+ # show add to Committee button only if every person is not on the
Committee
+ if @people.all? {|person| !@@project.members.include? person.id}
+ _button.btn.btn_primary "Add to Committee",
+ data_action: 'add pmc info',
+ onClick: self.post, disabled: (@people.empty? or not
@notice_elapsed)
+ end
+
+ # remove from all relevant locations
+ remove_from = ['commit']
+ if @people.any? {|person| @@project.members.include? person.id}
+ remove_from << 'info'
+ end
+ if @people.any? {|person| @@project.ldap.include? person.id}
+ remove_from << 'pmc'
+ end
+
+ _button.btn.btn_primary 'Remove from project', onClick: self.post,
+ data_action: "remove #{remove_from.join(' ')}"
+
+ if @people.all? {|person| @@project.members.include? person.id}
+ _button.btn.btn_warning "Remove from Committee only",
+ data_action: 'remove pmc info',
+ onClick: self.post
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/www/roster/views/nonpmc/nonpmc.js.rb
b/www/roster/views/nonpmc/nonpmc.js.rb
new file mode 100644
index 0000000..beb3c48
--- /dev/null
+++ b/www/roster/views/nonpmc/nonpmc.js.rb
@@ -0,0 +1,167 @@
+#
+# Show Committee members
+#
+
+class NonPMCMembers < Vue
+ def initialize
+ @nonpmc = {}
+ @committers = []
+ end
+
+ def render
+ _h2.pmc! 'Committee (' + roster.length + ')'
+ _p 'Click on column name to sort'
+ _table.table.table_hover do
+ _thead do
+ _tr do
+ _th if @@auth
+ _th 'id', data_sort: 'string'
+ _th 'githubUsername', data_sort: 'string'
+ _th.sorting_asc 'public name', data_sort: 'string-ins'
+ _th 'starting date', data_sort: 'string'
+ _th 'status - click cell for actions', data_sort: 'string'
+ end
+ end
+
+ _tbody do
+ roster.each do |person|
+ _NonPMCMember auth: @@auth, person: person, nonpmc: @@nonpmc
+ end
+ end
+ end
+ end
+
+ def mounted()
+ jQuery('.table', $el).stupidtable()
+ end
+
+ def roster
+ result = []
+
+ for id in @@nonpmc.roster
+ if @@nonpmc.members.include?(id) or @@nonpmc.ldap.include?(id)
+ person = @@nonpmc.roster[id]
+ person.id = id
+ result << person
+ end
+ end
+
+ result.sort_by {|person| person.name}
+ end
+end
+
+#
+# Show a member of the Committee
+#
+
+class NonPMCMember < Vue
+ def initialize
+ @state = :closed
+ end
+
+ def render
+ _tr do
+ if @@auth
+ _td do
+ _input type: 'checkbox', checked: @@person.selected || false,
+ onClick: -> {self.toggleSelect(@@person)}
+ end
+ end
+ if @@person.member
+ _td { _b { _a @@person.id, href: "committer/#{@@person.id}" }
+ _a ' (*)', href: "nonpmc/#{@@nonpmc.id}#crosscheck" if
@@person.notSubbed and @@nonpmc.analysePrivateSubs
+ }
+ _td @@person.githubUsername
+ _td { _b @@person.name }
+ else
+ _td { _a @@person.id, href: "committer/#{@@person.id}"
+ _a ' (*)', href: "nonpmc/#{@@nonpmc.id}#crosscheck" if
@@person.notSubbed and @@nonpmc.analysePrivateSubs
+ }
+ _td @@person.githubUsername
+ _td @@person.name
+ end
+
+ _td @@person.date
+
+ if @state == :open
+ _td data_ids: @@person.id, onDoubleClick: self.select do
+ if not @@person.date
+ # in LDAP but not in committer-info.txt
+ _button.btn.btn_warning 'Remove from LDAP',
+ data_action: 'remove pmc',
+ data_target: '#confirm', data_toggle: 'modal',
+ data_confirmation: "Remove #{@@person.name} from LDAP?"
+
+ unless @@nonpmc.roster.keys().empty?
+ _button.btn.btn_success 'Add to committer-info.txt',
+ data_action: 'add info',
+ data_target: '#confirm', data_toggle: 'modal',
+ data_confirmation: "Add to #{@@person.name} committer-info.txt"
+ end
+ elsif not @@person.ldap
+ # in committer-info.txt but not in LDAP
+ _button.btn.btn_success 'Add to LDAP',
+ data_action: 'add pmc',
+ data_target: '#confirm', data_toggle: 'modal',
+ data_confirmation: "Add #{@@person.name} to LDAP?"
+
+ _button.btn.btn_warning 'Remove from committer-info.txt',
+ data_action: 'remove info',
+ data_target: '#confirm', data_toggle: 'modal',
+ data_confirmation:
+ "Remove #{@@person.name} from committer-info.txt?"
+ else
+ # in both LDAP and committer-info.txt
+ if @@nonpmc.committers.include? @@person.id
+ _button.btn.btn_warning 'Remove only from Committee',
+ data_action: 'remove pmc info',
+ data_target: '#confirm', data_toggle: 'modal',
+ data_confirmation: "Remove #{@@person.name} from the " +
+ "#{@@nonpmc.display_name} Committee but leave as a
committer?"
+
+ _button.btn.btn_warning 'Remove as committer and from Committee',
+ data_action: 'remove pmc info commit',
+ data_target: '#confirm', data_toggle: 'modal',
+ data_confirmation: "Remove #{@@person.name} as commiter and " +
+ "from the #{@@nonpmc.display_name} Committee?"
+ else
+ _button.btn.btn_warning 'Remove from Committee',
+ data_action: 'remove pmc info',
+ data_target: '#confirm', data_toggle: 'modal',
+ data_confirmation: "Remove #{@@person.name} from the " +
+ "#{@@nonpmc.display_name} Committee?"
+
+ _button.btn.btn_primary 'Add as a committer',
+ data_action: 'add commit',
+ data_target: '#confirm', data_toggle: 'modal',
+ data_confirmation: "Grant #{@@person.name} committer access?"
+ end
+ end
+ end
+ elsif not @@person.date
+ _td.issue.clickable 'not in committer-info.txt', onClick: self.select
+ elsif not @@person.ldap
+ _td.issue.clickable 'not in LDAP', onClick: self.select
+ elsif not @@nonpmc.committers.include? @@person.id
+ _td.issue.clickable 'not in committer list', onClick: self.select
+ elsif @@person.id == @@nonpmc.chair
+ _td.chair.clickable (@@nonpmc.pmc_chair ? 'chair' : 'chair (not in
pmc-chairs)'), onClick: self.select
+ else
+ _td.clickable '', onClick: self.select
+ end
+ end
+ end
+
+ # toggle display of buttons
+ def select()
+ return unless @@auth
+ window.getSelection().removeAllRanges()
+ @state = ( @state == :open ? :closed : :open )
+ end
+
+ # toggle checkbox
+ def toggleSelect(person)
+ person.selected = !person.selected
+ @@nonpmc.refresh()
+ end
+end
diff --git a/www/roster/views/nonpmcs.html.rb b/www/roster/views/nonpmcs.html.rb
new file mode 100644
index 0000000..adedeec
--- /dev/null
+++ b/www/roster/views/nonpmcs.html.rb
@@ -0,0 +1,63 @@
+#
+# List of committees
+#
+
+_html do
+ _base href: '..'
+ _link rel: 'stylesheet', href: "stylesheets/app.css?#{cssmtime}"
+ _whimsy_body(
+ title: 'ASF Committees Listing (non-PMC)',
+ breadcrumbs: {
+ roster: '.',
+ nonpmc: 'nonpmc/'
+ }
+ ) do
+ _p do
+ _ 'A full list of Apache committees that are not PMCs; click on the name
for a detail page about that committee. Other groups of various kinds '
+ _a href: '/roster/group/' do
+ _span.glyphicon.glyphicon_lock :aria_hidden, class: 'text-primary',
aria_label: 'ASF Members Private'
+ _ 'are listed privately.'
+ end
+ end
+ _p 'Click on column names to sort.'
+
+ _table.table.table_hover do
+ _thead do
+ _tr do
+ _th.sorting_asc 'Name', data_sort: 'string-ins'
+ _th 'Chair(s)', data_sort: 'string'
+ _th 'Description', data_sort: 'string'
+ end
+ end
+
+ @nonpmcs.sort_by {|pmc| pmc.display_name.downcase}.each do |pmc|
+ _tr_ do
+ _td do
+ _a pmc.display_name, href: "nonpmc/#{pmc.name}"
+ end
+
+ _td do
+ pmc.chairs.each_with_index do |chair, index|
+ _span ', ' unless index == 0
+
+ if @members.include? chair[:id]
+ _b! {_a chair[:name], href: "committer/#{chair[:id]}"}
+ else
+ _a chair[:name], href: "committer/#{chair[:id]}"
+ end
+ end
+ end
+
+# if not pmc.established
+# _td.issue 'Not in committee-info.txt'
+# else
+ _td pmc.description
+# end
+ end
+ end
+ end
+ end
+ _script %{
+ $(".table").stupidtable();
+ }
+end