www/board/agenda/views/pages/report.js.rb (284 lines of code) (raw):

# # A two section representation of an agenda item (typically a PMC report), # where the two sections will show up as two columns on wide enough windows. # # The first section contains the item text, with a missing indicator if # the report isn't present. It also contains an inline copy of draft # minutes for agenda items in section 3. # # The second section contains posted comments, pending comments, and # action items associated with this agenda item. # # Filters may be used to highlight or hypertext link portions of the text. # class Report < Vue def render _section.flexbox do _section do if @@item.warnings _ul.missing @@item.warnings do |warning| _li warning end end _pre.report do if @@item.text _Text raw: @@item.text, filters: filters elsif @@item.missing draft = Reporter.find(@@item) if draft _p do _em 'Unposted draft being prepared at ' _a 'reporter.apache.org', href: "https://reporter.apache.org/wizard?#{draft.project}" _span ':' end _Text raw: draft.text, filters: [self.draft] else _p {_em 'Missing'} end else _p {_em 'Empty'} end end if (@@item.missing or @@item.comments) and @@item.mail_list _section.reminder do if @@item.missing if not Posted.get(@@item.title).empty? or Reporter.find(@@item) _button.btn.btn_primary 'post report', data_toggle: 'modal', data_target: '#post-report-form' elsif @@item.attach =~ /^[A-Z]/ and User.firstname and @@item.shepherd and User.firstname.start_with? @@item.shepherd.downcase() then _p.comment do _ 'No report was found on ' _a 'board@apache.org', href: 'https://lists.apache.org/list.html?board@apache.org' _ ' archives since the last board report. If/when a report' _ ' is posted there with a ' _tt '[Report]' _ ' tag in the subject line a POST button will appear here ' _ ' to assist with the posting the report.' end end end _Email item: @@item end end if minutes _pre.comment do if minutes === :missing _p { _em 'missing' } else _Text raw: minutes, filters: [hotlink] end end end end _section do _AdditionalInfo item: @@item _div.report_info do _h4 'Report Info' _Info item: @@item end end end end # determine what text filters to run def filters list = [self.linebreak, self.todo, hotlink, self.privates, self.jira, self.cve] list = [self.localtime, hotlink] if @@item.title == 'Call to order' list << self.names if @@item.people list << self.president_attachments if @@item.title == 'President' list << self.linkMinutes if @@item.attach =~ /^[37][A-Z]$/ list end # special processing for Minutes from previous meetings def minutes if @@item.attach =~ /^3[A-Z]$/ # if draft is available, fetch minutes for display date = @@item.text[/board_minutes_(\d+_\d+_\d+)\.txt/, 1] if date and not defined? @@item.minutes and defined? XMLHttpRequest if @@item.mtime Vue.set @@item, 'minutes', '' retrieve "minutes/#{date}?#{@@item.mtime}", :text do |minutes| @@item.minutes = minutes end else @@item.minutes = :missing end end end @@item.minutes end # ### filters # # Highlight todos def todo(text) return text.gsub 'TODO', '<span class="missing">TODO</span>' end # Break long lines, treating HTML Entities (like &amp;) as one character def linebreak(text) # find long, breakable lines regex = Regexp.new(/(\&\w+;|.){80}.+/, 'g') result = nil indices = []; while result = regex.exec(text) line = result[0] break if line.gsub(/\&\w+;/, '.').length < 80 lastspace = /^.*\s\S/.exec(line) if lastspace and lastspace[0].gsub(/\&\w+;/, '.').length - 1 > 40 indices.unshift([line, result.index]) end end # reflow each line found indices.each do |info| line = info[0] index = info[1] replacement = '<span class="hilite" title="reflowed">' + Flow.text(line) + '</span>' text = text.slice(0, index) + replacement + text.slice(index + line.length) end return text end # Convert start time to local time on Call to order page def localtime(text) return text.sub /\n(\s+)(Other Time Zones:.*)/ do |match, spaces, text| localtime = Date.new(@@item.timestamp).toLocaleString() "\n#{spaces}<span class='hilite'>" + "Local Time: #{localtime}</span>#{spaces}#{text}" end end # replace ids with committer links def names(text) roster = '/roster/committer/' @@item.people.each_pair do |id, person| # email addresses in 'Establish' resolutions and (ids) everywhere text.gsub! /(\(|&lt;)(#{escapeRegExp(id)})( at |@|\))/ do |m, pre, id, post| if person.icla if post == ')' and person.member "#{pre}<b><a href='#{roster}#{id}'>#{id}</a></b>#{post}" else "#{pre}<a href='#{roster}#{id}'>#{id}</a>#{post}" end else "#{pre}<a class='missing' href='#{roster}?q=#{person.name}'>" + "#{id}</a>#{post}" end end # names if person.icla or @@item.title == 'Roll Call' pattern = escapeRegExp(person.name).gsub(/ +/, '\s+') if defined? person.member text.gsub! /#{pattern}/ do |match| "<a href='#{roster}#{id}'>#{match}</a>" end else text.gsub! /#{pattern}/ do |match| "<a href='#{roster}?q=#{person.name}'>#{match}</a>" end end end # highlight potentially misspelled names if person.icla and not person.icla == person.name names = person.name.split(/\s+/) iclas = person.icla.split(/\s+/) ok = false ok ||= names.all? {|part| iclas.any? {|icla| icla.include? part}} ok ||= iclas.all? {|part| names.any? {|name| name.include? part}} if @@item.title =~ /^Establish/ and not ok text.gsub! /#{escapeRegExp("#{id}'>#{escapeRegExp(person.name)}")}/, "?q=#{encodeURIComponent(person.name)}'>" + "<span class='commented'>#{person.name}</span>" else text.gsub! /#{escapeRegExp(person.name)}/, "<a href='#{roster}#{id}'>#{person.name}</a>" end end # put members names in bold if person.member pattern = escapeRegExp(person.name).gsub(/ +/, '\s+') text.gsub!(/#{pattern}/) {|match| "<b>#{match}</b>"} end end # treat any unmatched names in Roll Call as misspelled if @@item.title == 'Roll Call' text.gsub! /(\n\s{4})([A-Z].*)/ do |match, space, name| "#{space}<a class='commented' href='#{roster}?q=#{name}'>#{name}</a>" end end # highlight any non-apache.org email addresses in establish resolutions if @@item.title =~ /^Establish/ text.gsub! /(&lt;|\()[-.\w]+@(([-\w]+\.)+\w+)(&gt;|\))/ do |match| if match =~ /@apache\.org/ match else '<span class="commented" title="non @apache.org email address">' + match + '</span>' end end end # highlight mis-spelling of previous and proposed chair names if @@item.title.start_with? 'Change' and text =~ /\(\w[-_.\w]+\)/ text.sub!(/heretofore\s+appointed\s+(\w(\s|.)*?)\s+\(/) do |text, name| text.sub(name, "<span class='hilite'>#{name}</span>") end text.sub!(/chosen\sto\s+recommend\s+(\w(\s|.)*?)\s+\(/) do |text, name| text.sub(name, "<span class='hilite'>#{name}</span>") end end return text end # link to board minutes and other attachments def linkMinutes(text) text.gsub! /board_minutes_(\d+)_\d+_\d+\.txt/ do |match, year| if Server.drafts.include? match link = "https://svn.apache.org/repos/private/foundation/board/#{match}" else link = "http://apache.org/foundation/records/minutes/#{year}/#{match}" end "<a href='#{link}'>#{match}</a>" end footer = '' text.gsub! /Attachment (\w+)/ do |match, attach| item = Agenda.index.find {|item| item.attach == attach} if item footer += "<hr/><h4>#{match}</h4><pre>#{item.text}</pre>" "<a href='#{item.title.gsub(' ', '-')}'>#{match}</a>" else match end end return text + footer end # highlight private sections - these sections appear in the agenda but # will be removed when the minutes are produced (see models/minutes.rb) def privates(text) # block of lines (and preceding whitespace) where the first line starts # with <private> and the last line ends </private>. private_lines = Regexp.new('^([ \t]*&lt;private&gt;(?:\n|.)*?&lt;/private&gt;)(\s*)$', 'mig') # mark private sections with class private text.gsub!(private_lines) do |match, text| "<div class='private'>#{text}</div>" end # flag remaining private markers private_tag =/(\s*.\s*)(&lt;\/?private&gt;)(\s*.\s*)/i text.gsub! private_tag do |match, before, text, after| if before.include? '>' or after.include? '<' match elsif before.include? "\n" or after.include? "\n" match else "#{before}<span class='error' " + "title='private sections must consist only of full lines of text'" + ">#{text}</span>#{after}" end end return text end # expand president's attachments def president_attachments(text) match = text.match(/Additionally, please see Attachments (\d) through (\d\d?)\./) if match agenda = Agenda.index for i in 0...agenda.length next unless agenda[i].attach =~ /^\d$/ if agenda[i].attach >= match[1] and agenda[i].attach <= match[2] text += "\n #{agenda[i].attach}. " + "<a #{ agenda[i].text.empty? ? 'class="pres-missing" ' : ''}" + "href='#{agenda[i].href}'>#{agenda[i].title}</a>" end end end return text end # hotlink to JIRA issues def jira(text) jira_issue = Regexp.new(/(^|\s|\(|\[)([A-Z][A-Z0-9]+)-([1-9][0-9]*) (\.(\D|$)|[,;:\s)\]]|$)/x, 'g') text.gsub! jira_issue do |m, pre, name, issue, post| if JIRA.find(name) return "#{pre}<a target='_self' " + "href='https://issues.apache.org/jira/browse/#{name}-#{issue}'>" + "#{name}-#{issue}</a>#{post}" else return "#{pre}#{name}-#{issue}#{post}" end end return text end # hotlink to CVE def cve(text) return text.gsub(/\bCVE-\d{4}-\d{4,}\b/) do |id| "<a href='https://cve.mitre.org/cgi-bin/cvename.cgi?name=#{id}'>#{id}</a>" end end def draft(text) return "<div class='private'>#{text}</div>" end end