diff --git a/prs_per_repo.rb b/prs_per_repo.rb index b791dd9..80a2c04 100755 --- a/prs_per_repo.rb +++ b/prs_per_repo.rb @@ -5,29 +5,32 @@ require 'optimist' class PrsPerRepo - attr_reader :config, :config_file, :output_file, :sprint + attr_reader :opts, :config, :output_file, :sprint def initialize(opts) - @sprint = find_sprint(opts[:sprint]) - if @sprint.nil? - STDERR.puts "invalid sprint specified" - exit - end - - @config_file = opts[:config_file] - @config = YAML.load_file(@config_file) + @opts = opts + config_file = opts[:config_file] + @config = YAML.load_file(config_file) @output_file = opts[:output_file] || "prs_per_repo.csv" + + repo_given = opts[:repo_slug_given] ? opts[:repo_slug] : config[:repo_slug] + @repos = repo_given ? Array(repo_given) : stats.default_repos.sort + @github_org = repo_given ? "" : "ManageIQ" + @sprint = get_sprint(opts) end - def find_sprint(sprint = nil) + def get_sprint(opts) + sprint_given = opts[:sprint_given] ? opts[:sprint] : config[:sprint] + + return Sprint.prompt_for_sprint(3) unless sprint_given + + sprint = (sprint_given == 'last') ? Sprint.last_completed : Sprint.create_by_sprint_number(sprint_given.to_i) if sprint.nil? - Sprint.prompt_for_sprint(3) - elsif sprint == 'last' - Sprint.last_completed - else - Sprint.create_by_sprint_number(sprint.to_i) - end + STDERR.puts "invalid sprint <#{sprint_given}> specified" + exit + end + sprint end def github_api_token @@ -38,58 +41,100 @@ def stats @stats ||= SprintStatistics.new(github_api_token) end - def merged?(repo, number) - stats.client.pull_request(repo, number).merged? + def repo_in_org?(repo, org) + repo.split('/').first == org + end + + def execute_query(query) + results = stats.client.search_issues(query) +puts "github query=#{query.inspect} returned #{results.total_count} items" +#puts "query results=#{results.inspect}" + results.items + end + + def cached_execute_query(query) + @query_cache ||= {} + @query_cache[query] ||= execute_query(query) + @query_cache[query] + end + + def execute_query_in_repo_or_org(repo, query) + if repo_in_org?(repo, @github_org) + results = cached_execute_query "org:#{@github_org} #{query}" + results.select { |pr| pr.repository_url.end_with?(repo) } + else + cached_execute_query "repo:#{repo} #{query}" + end + end + + def remaining_open(repo) + execute_query_in_repo_or_org(repo, "type:pr state:open created:<=#{@sprint.ended_iso8601}") + end + + def closed_after_sprint(repo) + execute_query_in_repo_or_org(repo, "type:pr state:closed created:<=#{@sprint.ended_iso8601} closed:>#{@sprint.ended_iso8601}") + end + + def closed_during_sprint_search_query + "type:pr state:closed created:<=#{@sprint.ended_iso8601} closed:#{@sprint.range_iso8601}" + end + + def closed_during_sprint(repo) + execute_query_in_repo_or_org(repo, closed_during_sprint_search_query) + end + + def closed_merged_during_sprint(repo) + execute_query_in_repo_or_org(repo, "is:merged #{closed_during_sprint_search_query}") end - def prs_since_sprint_start(repo) - since = @sprint.range.begin.in_time_zone("US/Pacific").iso8601 - stats.pull_requests(repo, :state => :all, :since => since) + def closed_unmerged_during_sprint(repo) + execute_query_in_repo_or_org(repo, "is:unmerged #{closed_during_sprint_search_query}") end - def open_prs(repo) - stats.pull_requests(repo, :state => :open) + def created_during_sprint(repo) + execute_query_in_repo_or_org(repo, "type:pr created:#{@sprint.range_iso8601}") end LABELS = ["bug", "enhancement", "developer", "documentation", "performance", "refactoring", "technical debt", "test"] def process_repo(repo) - puts "Collecting pull_requests for: #{repo}" - opened = 0 - closed_merged = [] - closed_unmerged = [] - labels_arr = [] - prs_remaining_open = open_prs(repo).length - - prs_since_sprint_start(repo).each do |pr| - next if @sprint.after_range?(pr.created_at) # skip PRs opened after the end of the sprint - - opened += 1 if @sprint.in_range?(pr.created_at) - - if @sprint.in_range?(pr.closed_at) - if merged?(repo, pr.number) - closed_merged << pr - pr.labels.each { |label| labels_arr << label.name } - else - closed_unmerged << pr - end - else - # Add to remaining open any PRs that were closed AFTER the sprint ended - prs_remaining_open += 1 if pr.closed_at && @sprint.after_range?(pr.closed_at) - end + puts "Analyzing Repo: #{repo}" + + stats = {} + stats[:prs] = {} + stats[:counts] = {} + + opened = created_during_sprint(repo) + stats[:prs][:opened] = opened.collect(&:number).sort + stats[:counts][:opened] = opened.length + + still_open = remaining_open(repo) + closed_after_sprint(repo) + stats[:prs][:still_open] = still_open.collect(&:number).sort + stats[:counts][:still_open] = still_open.length + + closed_merged = closed_merged_during_sprint(repo) + closed_unmerged = closed_unmerged_during_sprint(repo) + labels_array = [] + closed_merged.each do |pr| + pr.labels.each { |label| labels_array << label.name } end - merged_labels_hash = labels_arr.element_counts + + merged_labels_hash = labels_array.element_counts labels_string = merged_labels_hash.values_at(*LABELS).collect(&:to_i).join(",") - puts " Closed/Unmerged: #{closed_unmerged.collect(&:html_url).inspect}" - puts " Closed/Merged: #{closed_merged.collect(&:html_url).inspect}" - puts " Closed/Merged Labels: #{merged_labels_hash.inspect}" + stats[:prs][:closed_unmerged] = closed_unmerged.collect(&:number).sort + stats[:counts][:closed_unmerged] = closed_unmerged.length + stats[:prs][:closed_merged] = closed_merged.collect(&:number).sort + stats[:counts][:closed_merged] = closed_merged.length + stats[:merged_labels] = merged_labels_hash + puts "#{repo} stats: #{stats.inspect}" + puts "Analyzing Repo: #{repo} completed" - return "#{repo},#{opened},#{closed_merged.length},#{labels_string},#{prs_remaining_open}" + return "#{repo},#{stats[:counts][:opened]},#{stats[:counts][:closed_merged]},#{labels_string},#{stats[:counts][:still_open]}" end def process_repos - results = stats.default_repos.sort.collect do |repo| + results = @repos.collect do |repo| process_repo(repo) end @@ -110,6 +155,13 @@ def self.parse(args) :type => :string, :required => false + opt :repo_slug, + "Repo Slug Name (e.g. ManageIQ/manageiq)", + :short => "r", + :default => nil, + :type => :string, + :required => false + opt :config_file, "Config file name", :short => "c", diff --git a/sprint.rb b/sprint.rb index d0deb05..4f02cde 100644 --- a/sprint.rb +++ b/sprint.rb @@ -59,6 +59,18 @@ def in_range?(timestamp) range.include?(timestamp.to_date) end + def began_iso8601 + range.first.iso8601 + end + + def ended_iso8601 + range.end.iso8601 + end + + def range_iso8601 + "#{began_iso8601}..#{ended_iso8601}" + end + def date range.end end