From 563af11aebb7509e77f602215fe957c3b1c07508 Mon Sep 17 00:00:00 2001 From: Oleg Barenboim Date: Mon, 4 May 2020 13:49:45 -0400 Subject: [PATCH 1/4] Optimized by using GitHub Search See https://help.github.com/en/github/searching-for-information-on-github/searching-issues-and-pull-requests --- prs_per_repo.rb | 113 +++++++++++++++++++++++++++++++++--------------- sprint.rb | 12 +++++ 2 files changed, 90 insertions(+), 35 deletions(-) diff --git a/prs_per_repo.rb b/prs_per_repo.rb index b791dd9..167740c 100755 --- a/prs_per_repo.rb +++ b/prs_per_repo.rb @@ -18,6 +18,7 @@ def initialize(opts) @config = YAML.load_file(@config_file) @output_file = opts[:output_file] || "prs_per_repo.csv" + @github_org = "ManageIQ" end def find_sprint(sprint = nil) @@ -38,54 +39,96 @@ 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 prs_since_sprint_start(repo) - since = @sprint.range.begin.in_time_zone("US/Pacific").iso8601 - stats.pull_requests(repo, :state => :all, :since => since) + 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 open_prs(repo) - stats.pull_requests(repo, :state => :open) + 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 closed_unmerged_during_sprint(repo) + execute_query_in_repo_or_org(repo, "is:unmerged #{closed_during_sprint_search_query}") + end + + 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 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 From 8c98cb01aa333361ee541d82249cd12d2229fd22 Mon Sep 17 00:00:00 2001 From: Oleg Barenboim Date: Mon, 4 May 2020 14:00:14 -0400 Subject: [PATCH 2/4] Remember the options passed and expose them via attr_reader --- prs_per_repo.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/prs_per_repo.rb b/prs_per_repo.rb index 167740c..dd97ef6 100755 --- a/prs_per_repo.rb +++ b/prs_per_repo.rb @@ -5,17 +5,12 @@ 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" @github_org = "ManageIQ" From eb9f5c3a656923c61383d36466116bc08cba907f Mon Sep 17 00:00:00 2001 From: Oleg Barenboim Date: Mon, 4 May 2020 13:52:36 -0400 Subject: [PATCH 3/4] Added support to specify sprint in config file --- prs_per_repo.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/prs_per_repo.rb b/prs_per_repo.rb index dd97ef6..233e1b0 100755 --- a/prs_per_repo.rb +++ b/prs_per_repo.rb @@ -14,16 +14,20 @@ def initialize(opts) @output_file = opts[:output_file] || "prs_per_repo.csv" @github_org = "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 From 6e36abd322bc5d7238b0e06beea83cf2c2804cb7 Mon Sep 17 00:00:00 2001 From: Oleg Barenboim Date: Mon, 4 May 2020 14:01:26 -0400 Subject: [PATCH 4/4] Add support to pass in repo_slug --- prs_per_repo.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/prs_per_repo.rb b/prs_per_repo.rb index 233e1b0..80a2c04 100755 --- a/prs_per_repo.rb +++ b/prs_per_repo.rb @@ -13,7 +13,10 @@ def initialize(opts) @config = YAML.load_file(config_file) @output_file = opts[:output_file] || "prs_per_repo.csv" - @github_org = "ManageIQ" + + 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 @@ -131,7 +134,7 @@ def process_repo(repo) end def process_repos - results = stats.default_repos.sort.collect do |repo| + results = @repos.collect do |repo| process_repo(repo) end @@ -152,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",