whimsical-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From s...@apache.org
Subject [whimsy] branch master updated: Add new code to display non-PMC committees
Date Thu, 10 Jan 2019 21:22:42 GMT
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 <sebb@apache.org>
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


Mime
View raw message