Skip to content

Commit

Permalink
Merge pull request #48 from chessbyte/prs_per_repo_should_optimize_in…
Browse files Browse the repository at this point in the history
…teractions_with_github

prs_per_repo script should optimize interactions with GitHub
  • Loading branch information
Fryguy authored May 4, 2020
2 parents 9119559 + 6e36abd commit def1604
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 52 deletions.
156 changes: 104 additions & 52 deletions prs_per_repo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions sprint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit def1604

Please sign in to comment.