# frozen_string_literal: true require 'octokit' require 'date' class ReviewReport def initialize(token:, repo:, branch:, duration:) @token = token @repo = repo @branch = branch @duration_in_days = duration end def percentile(array, percentile) sorted_array = array.sort k = (percentile * (sorted_array.length - 1)).floor l = k + 1 q1 = sorted_array[k] q2 = sorted_array[l] q1 + (q2 - q1) * (percentile * (sorted_array.length - 1) - k) end def report client = Octokit::Client.new(access_token: @token) client.auto_paginate = true # Fetch pull requests created after the target date pull_requests = client.list_issues(@repo, state: 'closed', since: DateTime.now - @duration_in_days) # Group pull requests by week pull_requests_by_week = pull_requests.group_by { |pr| pr[:closed_at].strftime('%Y-%W') } weeks = @duration_in_days / 7 # Iterate over months within the days duration limit pull_requests_by_week.keys.sort[-weeks..].each do |week| total_time_seconds = 0 total_count = 0 total_count_calc = 0 total_reviews = 0 reviews_with_comments = 0 change_requested_reviews = 0 # Iterate over pull requests in the week pull_requests_by_week[week].each do |issue| pull_request = client.pull_request(@repo, issue.number) last_commit_sha = pull_request[:head][:sha] last_commit = client.commit(@repo, last_commit_sha) reviews = [] review_page = 1 loop do response = client.pull_request_reviews(@repo, pull_request[:number], per_page: 100, page: review_page) break if response.empty? reviews.concat(response) review_page += 1 end total_reviews += reviews.size next unless reviews.any? last_review = reviews.max_by { |review| review[:submitted_at] } time_taken_seconds = (last_review[:submitted_at] - last_commit[:commit][:author][:date]).abs if time_taken_seconds / 3600.0 < 8 && time_taken_seconds / 3600.0 > 0.3 total_time_seconds += time_taken_seconds total_count_calc += 1 end total_count += 1 # Count reviews with comments reviews_with_comments += reviews.count do |review| comments = client.pull_request_review_comments(@repo, pull_request[:number], review[:id]) comments.any? end # Count reviews with "change_requested" state change_requested_reviews += reviews.count { |review| review[:state] == 'CHANGES_REQUESTED' } end if total_count.positive? average_time_hours = total_time_seconds / total_count_calc / 3600.0 # Convert seconds to hours puts "Calendar week #{week}: Average time taken for #{total_count} pull requests is #{average_time_hours.round(2)} hours." puts " Total reviews: #{total_reviews}" puts " Reviews with comments: #{reviews_with_comments}" puts " Change requested reviews: #{change_requested_reviews}" # ENV['BQ_CREDENTIALS'] = `cat /Users/serghei.moret/.config/gcloud/application_default_credentials.json` if ENV['BQ_CREDENTIALS'] require 'google/cloud/bigquery' require 'json' creds = JSON.parse(ENV['BQ_CREDENTIALS']) bigquery = Google::Cloud::Bigquery.new( project_id: 'hellofresh-android', credentials: creds ) dataset = bigquery.dataset 'github_data' query = <<~SQL MERGE INTO pr_reviews AS target USING (SELECT '#{week}' AS calendar_week, '#{@repo}' AS platform) AS source ON target.calendar_week = source.calendar_week AND target.platform = source.platform WHEN MATCHED THEN UPDATE SET target.change_requested_reviews = #{change_requested_reviews}, target.reviews_with_comments = #{reviews_with_comments}, target.total_reviews = #{total_reviews}, target.average_review_time_hours = #{average_time_hours.round(2)}, target.total_prs = #{total_count}, target.platform = '#{@repo}' WHEN NOT MATCHED THEN INSERT (calendar_week, total_prs, average_review_time_hours, total_reviews, reviews_with_comments, change_requested_reviews, platform) VALUES ('#{week}', #{total_count}, #{average_time_hours.round(2)}, #{total_reviews}, #{reviews_with_comments}, #{change_requested_reviews}, '#{@repo}'); SQL dataset.query(query) end else puts "#{week}: No pull requests with reviews." end end end end