github_repo_statistics 2.3.6 → 2.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/bin/github_repo_statistics +28 -5
- data/bin/release_merge_report +1 -2
- data/github_repo_statistics.gemspec +2 -2
- data/lib/github_repo_statistics/force_merge_report.rb +46 -48
- data/lib/github_repo_statistics/github_repo_statistics.rb +69 -34
- data/lib/github_repo_statistics/release_merge_report.rb +34 -33
- data/lib/github_repo_statistics/review_report.rb +18 -18
- data/lib/github_repo_statistics/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 45f9b41fdeece55840918ee51ada62d6c23509620e50c19e5a495df7bea0d0a8
|
|
4
|
+
data.tar.gz: 1a128c270392e20ed6b0d48db13cac15f3d1dfd41ab5b5f8b0f97d46c263df9b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 00c4d3572b32f831af53cec591c415828f1326875794da0df1b1009880ffd90cd1c99c01e8a6cc56b8d40ccd113fae09ecbb65d826ab273e635d1e036d782d29
|
|
7
|
+
data.tar.gz: ceadd24b9ccc16eedaf41a99e9dc1ef7f7843994b162c0c4a89ba829528197f85715bc29305a0f6a3bbde6436f5aed7ef2ce7a716ff74168f2f72ef79e15554a
|
data/Gemfile.lock
CHANGED
data/bin/github_repo_statistics
CHANGED
|
@@ -78,6 +78,11 @@ OptionParser.new do |opts|
|
|
|
78
78
|
options[:code_extension] = code_extension
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
opts.on('--percentile STRING',
|
|
82
|
+
'The percentile of file changes that are considered to be hotspot [default: "100"]') do |percentile|
|
|
83
|
+
options[:percentile] = percentile
|
|
84
|
+
end
|
|
85
|
+
|
|
81
86
|
opts.on('--team-to-focus STRING',
|
|
82
87
|
'The team identifier to focus for the metric collection [default: ""]') do |team_to_focus|
|
|
83
88
|
options[:team_to_focus] = team_to_focus
|
|
@@ -93,6 +98,11 @@ OptionParser.new do |opts|
|
|
|
93
98
|
options[:file_output] = true
|
|
94
99
|
end
|
|
95
100
|
|
|
101
|
+
opts.on('--hotspot-check',
|
|
102
|
+
'Enables a hotspot check [default:false]') do
|
|
103
|
+
options[:hotspot_check] = true
|
|
104
|
+
end
|
|
105
|
+
|
|
96
106
|
opts.on('-v', '--version', 'Display the version of the gem') do
|
|
97
107
|
puts "github_repo_statistics version #{GithubRepoStatistics::VERSION}"
|
|
98
108
|
exit
|
|
@@ -124,6 +134,7 @@ EXCLUDED_FILES = options[:excluded_files]
|
|
|
124
134
|
EXCLUDED_PRS = options[:excluded_prs]
|
|
125
135
|
TEAM_TO_FOCUS = options[:team_to_focus]
|
|
126
136
|
CODEOWNER_TO_FOCUS = options[:codeowner_to_focus]
|
|
137
|
+
PERCENTILE = options[:percentile] || '100'
|
|
127
138
|
|
|
128
139
|
unless CI
|
|
129
140
|
puts "\nDirectory: #{REPO_PATH}\n"
|
|
@@ -142,8 +153,20 @@ unless CI
|
|
|
142
153
|
puts "Debug mode is: #{options[:debug] ? 'on' : 'off'}\n\n"
|
|
143
154
|
end
|
|
144
155
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
GithubRepoStatistics.new(duration_in_days: options[:duration_in_days] || 30, directory_path: REPO_PATH,
|
|
149
|
-
|
|
156
|
+
if options[:hotspot_check]
|
|
157
|
+
feature_branch = `git rev-parse --abbrev-ref HEAD`.chomp
|
|
158
|
+
changed_files = `git diff --name-only master...#{feature_branch} | grep #{REPO_PATH} | grep '\.swift'`.split
|
|
159
|
+
branch_hotspot_files = GithubRepoStatistics.new(duration_in_days: options[:duration_in_days] || 30, directory_path: REPO_PATH,
|
|
160
|
+
begin_time: DateTime.now, steps: options[:steps].to_i, debug: options[:debug]).hotspot_check(files: changed_files, branch: feature_branch)
|
|
161
|
+
system("git checkout #{DEFAULT_BRANCH}", [:out] => File::NULL)
|
|
162
|
+
system('git pull', %i[out err] => File::NULL)
|
|
163
|
+
master_hotspot_files = GithubRepoStatistics.new(duration_in_days: options[:duration_in_days] || 30, directory_path: REPO_PATH,
|
|
164
|
+
begin_time: DateTime.now, steps: options[:steps].to_i, debug: options[:debug]).hotspot_check(files: changed_files, branch: DEFAULT_BRANCH)
|
|
165
|
+
|
|
166
|
+
raise 'New hotspot was introduced, contact foundation to unblock the PR' if branch_hotspot_files > master_hotspot_files
|
|
167
|
+
else
|
|
168
|
+
system("git checkout #{DEFAULT_BRANCH}", [:out] => File::NULL)
|
|
169
|
+
system('git pull', %i[out err] => File::NULL)
|
|
170
|
+
GithubRepoStatistics.new(duration_in_days: options[:duration_in_days] || 30, directory_path: REPO_PATH,
|
|
171
|
+
begin_time: DateTime.now, steps: options[:steps].to_i, debug: options[:debug]).contribution_message
|
|
172
|
+
end
|
data/bin/release_merge_report
CHANGED
|
@@ -7,7 +7,6 @@ require 'pry'
|
|
|
7
7
|
require_relative '../lib/github_repo_statistics/version'
|
|
8
8
|
require_relative '../lib/github_repo_statistics/release_merge_report'
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
options = {}
|
|
12
11
|
OptionParser.new do |opts|
|
|
13
12
|
opts.banner = 'Usage: github_repo_statistics [options]'
|
|
@@ -54,4 +53,4 @@ raise 'Please provide GitHub token using --github-token flag' if options[:github
|
|
|
54
53
|
raise 'Please provide GitHub repo name using --github-repo flag' if options[:github_repo].nil?
|
|
55
54
|
raise 'Please provide default GitHub branch using --default-branch flag' if DEFAULT_BRANCH.nil?
|
|
56
55
|
|
|
57
|
-
ReleaseMergeReport.new(token: options[:github_token], repo: options[:github_repo], branch_prefix:
|
|
56
|
+
ReleaseMergeReport.new(token: options[:github_token], repo: options[:github_repo], branch_prefix: 'release/').report
|
|
@@ -25,8 +25,8 @@ Gem::Specification.new do |spec|
|
|
|
25
25
|
spec.require_paths = ['lib']
|
|
26
26
|
|
|
27
27
|
spec.add_dependency 'date'
|
|
28
|
-
spec.add_dependency 'octokit'
|
|
29
|
-
spec.add_dependency 'pry'
|
|
30
28
|
spec.add_dependency 'faraday-retry'
|
|
31
29
|
spec.add_dependency 'google-cloud-bigquery'
|
|
30
|
+
spec.add_dependency 'octokit'
|
|
31
|
+
spec.add_dependency 'pry'
|
|
32
32
|
end
|
|
@@ -22,9 +22,7 @@ class ForceMergeReport
|
|
|
22
22
|
# Sort PRs into monthly chunks
|
|
23
23
|
weekly_pull_requests = pull_requests.group_by { |pr| pr.closed_at.strftime('%Y-%W') }
|
|
24
24
|
|
|
25
|
-
if weekly_pull_requests[Time.now.strftime('%Y-%W')].nil?
|
|
26
|
-
weekly_pull_requests[Time.now.strftime('%Y-%W')] = []
|
|
27
|
-
end
|
|
25
|
+
weekly_pull_requests[Time.now.strftime('%Y-%W')] = [] if weekly_pull_requests[Time.now.strftime('%Y-%W')].nil?
|
|
28
26
|
|
|
29
27
|
# Initialize a hash to store monthly summaries
|
|
30
28
|
weekly_summaries = Hash.new { |hash, key| hash[key] = { total: 0, failed: 0, workflows: Hash.new(0) } }
|
|
@@ -70,15 +68,15 @@ class ForceMergeReport
|
|
|
70
68
|
# Print details of merged pull requests without meeting the required criteria for the last 6 months
|
|
71
69
|
next if failed_checks.empty?
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
puts '---'
|
|
80
|
-
puts
|
|
71
|
+
next if @ci
|
|
72
|
+
|
|
73
|
+
puts "PR ##{pr.number} - Merged at: #{pr.merged_at}"
|
|
74
|
+
puts 'Failed Checks:'
|
|
75
|
+
failed_checks.each do |check|
|
|
76
|
+
puts "- Workflow: #{check.name || check.context}, Conclusion: #{check.conclusion || check.state}"
|
|
81
77
|
end
|
|
78
|
+
puts '---'
|
|
79
|
+
puts
|
|
82
80
|
end
|
|
83
81
|
end
|
|
84
82
|
|
|
@@ -93,43 +91,43 @@ class ForceMergeReport
|
|
|
93
91
|
|
|
94
92
|
# ENV['BQ_CREDENTIALS'] = `cat /Users/serghei.moret/.config/gcloud/application_default_credentials.json`
|
|
95
93
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
94
|
+
next unless ENV['BQ_CREDENTIALS']
|
|
95
|
+
|
|
96
|
+
require 'google/cloud/bigquery'
|
|
97
|
+
require 'json'
|
|
98
|
+
creds = JSON.parse(ENV['BQ_CREDENTIALS'])
|
|
99
|
+
bigquery = Google::Cloud::Bigquery.new(
|
|
100
|
+
project_id: 'hellofresh-android',
|
|
101
|
+
credentials: creds
|
|
102
|
+
)
|
|
103
|
+
dataset = bigquery.dataset 'github_data'
|
|
104
|
+
|
|
105
|
+
failed_count = summary[:failed]
|
|
106
|
+
size_check = summary[:workflows]['Mergeable: Size check']
|
|
107
|
+
sonarqube_check = summary[:workflows]['SonarQube Code Analysis']
|
|
108
|
+
ui_tests_check = summary[:workflows]['UI Tests']
|
|
109
|
+
unit_tests_check = summary[:workflows]['Unit Tests']
|
|
110
|
+
total_prs = summary[:total]
|
|
111
|
+
|
|
112
|
+
query = <<~SQL
|
|
113
|
+
MERGE INTO force_merges AS target
|
|
114
|
+
USING (SELECT '#{week}' AS calendar_week, '#{@repo}' AS platform) AS source
|
|
115
|
+
ON target.calendar_week = source.calendar_week AND target.platform = source.platform
|
|
116
|
+
WHEN MATCHED THEN
|
|
117
|
+
UPDATE SET
|
|
118
|
+
target.force_merges_count = #{failed_count},
|
|
119
|
+
target.ui_tests_count = #{ui_tests_check},
|
|
120
|
+
target.unit_tests_count = #{unit_tests_check},
|
|
121
|
+
target.size_check_count = #{size_check},
|
|
122
|
+
target.sonarqube_count = #{sonarqube_check},
|
|
123
|
+
target.total_prs = #{total_prs},
|
|
124
|
+
target.platform = '#{@repo}'
|
|
125
|
+
WHEN NOT MATCHED THEN
|
|
126
|
+
INSERT (calendar_week, force_merges_count, ui_tests_count, unit_tests_count, size_check_count, sonarqube_count, total_prs, platform)
|
|
127
|
+
VALUES ('#{week}', #{failed_count}, #{ui_tests_check}, #{unit_tests_check}, #{size_check}, #{sonarqube_check}, #{total_prs}, '#{@repo}');
|
|
128
|
+
SQL
|
|
129
|
+
|
|
130
|
+
dataset.query(query)
|
|
133
131
|
end
|
|
134
132
|
end
|
|
135
133
|
end
|
|
@@ -183,23 +183,23 @@ class GithubRepoStatistics
|
|
|
183
183
|
`git ls-tree -r --name-only $(git rev-list -1 HEAD) -- "#{directory_path}"`
|
|
184
184
|
end
|
|
185
185
|
|
|
186
|
-
def files_with_changes(directory_path:, start_date:, end_date:)
|
|
187
|
-
`git log origin
|
|
186
|
+
def files_with_changes(directory_path:, start_date:, end_date:, branch: DEFAULT_BRANCH)
|
|
187
|
+
`git log origin/#{branch} --name-only --pretty=format:"" --since="#{start_date}" --until="#{end_date}" "#{directory_path}"`
|
|
188
188
|
end
|
|
189
189
|
|
|
190
|
-
def git_commit_count(file:, start_date:, end_date:)
|
|
191
|
-
`git log origin
|
|
190
|
+
def git_commit_count(file:, start_date:, end_date:, branch: DEFAULT_BRANCH)
|
|
191
|
+
`git log origin/#{branch} --since="#{start_date}" --until="#{end_date}" --follow -- "#{file}" | grep -c '^commit'`
|
|
192
192
|
end
|
|
193
193
|
|
|
194
|
-
def git_commit_info(file:, start_date:, end_date:)
|
|
195
|
-
`git log origin
|
|
194
|
+
def git_commit_info(file:, start_date:, end_date:, branch: DEFAULT_BRANCH)
|
|
195
|
+
`git log origin/#{branch} --pretty=format:"%s" --since="#{start_date}" --until="#{end_date}" -- "#{file}"`
|
|
196
196
|
end
|
|
197
197
|
|
|
198
198
|
def new_changes?(file:)
|
|
199
|
-
git_commit_info(file:, start_date: DateTime.now - 7, end_date: DateTime.now)
|
|
199
|
+
git_commit_info(file:, start_date: DateTime.now - 7, end_date: DateTime.now) != ''
|
|
200
200
|
end
|
|
201
201
|
|
|
202
|
-
def analyze_changed_files(uniq_code_files_with_changes:, start_date:, end_date:)
|
|
202
|
+
def analyze_changed_files(uniq_code_files_with_changes:, start_date:, end_date:, branch: DEFAULT_BRANCH)
|
|
203
203
|
all_teams = []
|
|
204
204
|
cross_teams_count = 0
|
|
205
205
|
single_ownership_teams_count = 0
|
|
@@ -208,8 +208,8 @@ class GithubRepoStatistics
|
|
|
208
208
|
file_team_map = {}
|
|
209
209
|
uniq_code_files_with_changes.each do |file|
|
|
210
210
|
filename = File.basename(file)
|
|
211
|
-
commit_count = git_commit_count(file:, start_date:, end_date:).to_i
|
|
212
|
-
git_log = git_commit_info(file:, start_date:, end_date:).split("\n")
|
|
211
|
+
commit_count = git_commit_count(file:, start_date:, end_date:, branch:).to_i
|
|
212
|
+
git_log = git_commit_info(file:, start_date:, end_date:, branch:).split("\n")
|
|
213
213
|
|
|
214
214
|
if EXCLUDED_PRS
|
|
215
215
|
excluded_prs = EXCLUDED_PRS.split(',')
|
|
@@ -218,18 +218,17 @@ class GithubRepoStatistics
|
|
|
218
218
|
|
|
219
219
|
prs = git_log.map do |pr|
|
|
220
220
|
match = pr.match(/#(\d+)/)
|
|
221
|
-
if match
|
|
222
|
-
match[0]
|
|
223
|
-
end
|
|
221
|
+
match[0] if match
|
|
224
222
|
end.uniq
|
|
225
223
|
|
|
226
224
|
teams = git_log.map do |team|
|
|
227
225
|
team.match(/#{TEAM_REGEX}/)[0].upcase
|
|
228
226
|
end.reject { |e| EXCLUSIONS&.include?(e) }
|
|
229
227
|
|
|
228
|
+
teams = calculate_percentile(teams, PERCENTILE.to_i)
|
|
229
|
+
|
|
230
230
|
total_changes += commit_count
|
|
231
231
|
all_teams << teams
|
|
232
|
-
teams = teams.uniq
|
|
233
232
|
|
|
234
233
|
if teams.count > 1
|
|
235
234
|
files_changed_by_many_teams += 1
|
|
@@ -245,6 +244,30 @@ class GithubRepoStatistics
|
|
|
245
244
|
file_team_map]
|
|
246
245
|
end
|
|
247
246
|
|
|
247
|
+
def calculate_percentile(arr, percentile)
|
|
248
|
+
# Count occurrences of each unique element
|
|
249
|
+
counts = arr.each_with_object(Hash.new(0)) { |item, hash| hash[item] += 1 }
|
|
250
|
+
|
|
251
|
+
# Sort elements by their counts in descending order
|
|
252
|
+
sorted_counts = counts.sort_by { |_k, v| -v }.to_h
|
|
253
|
+
|
|
254
|
+
# Calculate the cut-off for the percentile
|
|
255
|
+
total_count = arr.size
|
|
256
|
+
cutoff = total_count * (percentile / 100.0)
|
|
257
|
+
|
|
258
|
+
# Select elements that meet the percentile criteria
|
|
259
|
+
selected_elements = []
|
|
260
|
+
cumulative_count = 0
|
|
261
|
+
|
|
262
|
+
sorted_counts.each do |item, count|
|
|
263
|
+
cumulative_count += count
|
|
264
|
+
selected_elements << item
|
|
265
|
+
break if cumulative_count >= cutoff
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
selected_elements
|
|
269
|
+
end
|
|
270
|
+
|
|
248
271
|
def filter_files(file_team_map:, size: BIG_FILE_SIZE)
|
|
249
272
|
file_team_map.select do |file_path|
|
|
250
273
|
next unless File.exist?(file_path)
|
|
@@ -270,7 +293,7 @@ class GithubRepoStatistics
|
|
|
270
293
|
occurrences = all_teams.flatten.compact.tally
|
|
271
294
|
sorted_occurrences = occurrences.sort_by { |element, count| [-count, element] }
|
|
272
295
|
contributors = Hash[sorted_occurrences]
|
|
273
|
-
churn_count = file_team_map.values.map
|
|
296
|
+
churn_count = file_team_map.values.map(&:last).sum
|
|
274
297
|
hotspot_changes_percentage = ((churn_count.to_f / total_changes) * 100).round(2)
|
|
275
298
|
# Filter files based on extension, existence and size
|
|
276
299
|
filtered_files = filter_files(file_team_map:)
|
|
@@ -283,14 +306,14 @@ class GithubRepoStatistics
|
|
|
283
306
|
# ENV['BQ_CREDENTIALS'] = `cat /Users/serghei.moret/.config/gcloud/application_default_credentials.json`
|
|
284
307
|
|
|
285
308
|
if ENV['BQ_CREDENTIALS']
|
|
286
|
-
require
|
|
287
|
-
require
|
|
309
|
+
require 'google/cloud/bigquery'
|
|
310
|
+
require 'json'
|
|
288
311
|
creds = JSON.parse(ENV['BQ_CREDENTIALS'])
|
|
289
312
|
bigquery = Google::Cloud::Bigquery.new(
|
|
290
|
-
project_id:
|
|
313
|
+
project_id: 'hellofresh-android',
|
|
291
314
|
credentials: creds
|
|
292
315
|
)
|
|
293
|
-
dataset = bigquery.dataset
|
|
316
|
+
dataset = bigquery.dataset 'github_data'
|
|
294
317
|
|
|
295
318
|
files_with_multiple_contributor = file_team_map.count
|
|
296
319
|
big_files_with_multiple_contributors = filtered_top_touched_files.count
|
|
@@ -303,24 +326,24 @@ class GithubRepoStatistics
|
|
|
303
326
|
end
|
|
304
327
|
|
|
305
328
|
query = <<~SQL
|
|
306
|
-
|
|
307
|
-
|
|
329
|
+
INSERT INTO modularization (date, platform, single_contributor_percentage, files_changed_by_many_teams, file_count, cross_teams_count, single_ownership_teams_count, hotspot_changes_percentage, churn_count, total_changes, files_with_multiple_contributor, big_files_with_multiple_contributors, total_files_changed, hotspot_lines, big_files_count)
|
|
330
|
+
VALUES ('#{@begin_time}', '#{platform}', #{files_with_single_contributor_percentage}, #{files_changed_by_many_teams}, #{file_count}, #{cross_teams_count}, #{single_ownership_teams_count}, #{hotspot_changes_percentage}, #{churn_count}, #{total_changes}, #{files_with_multiple_contributor}, #{big_files_with_multiple_contributors}, #{total_files_changed}, #{hotspot_lines}, #{big_files_count});
|
|
308
331
|
SQL
|
|
309
332
|
|
|
310
333
|
dataset.query(query)
|
|
311
334
|
|
|
312
|
-
# delete_query = <<~SQL
|
|
313
|
-
# DELETE FROM modularization
|
|
314
|
-
# WHERE CONCAT(DATE(date), ' ', TIME(date)) NOT IN (
|
|
315
|
-
# SELECT CONCAT(DATE(date), ' ', TIME(date))
|
|
316
|
-
# FROM modularization AS m1
|
|
317
|
-
# WHERE TIME(date) = (
|
|
318
|
-
# SELECT MAX(TIME(date))
|
|
319
|
-
# FROM modularization AS m2
|
|
320
|
-
# WHERE DATE(m1.date) = DATE(m2.date)
|
|
321
|
-
# )
|
|
322
|
-
# );
|
|
323
|
-
# SQL
|
|
335
|
+
# delete_query = <<~SQL
|
|
336
|
+
# DELETE FROM modularization
|
|
337
|
+
# WHERE CONCAT(DATE(date), ' ', TIME(date)) NOT IN (
|
|
338
|
+
# SELECT CONCAT(DATE(date), ' ', TIME(date))
|
|
339
|
+
# FROM modularization AS m1
|
|
340
|
+
# WHERE TIME(date) = (
|
|
341
|
+
# SELECT MAX(TIME(date))
|
|
342
|
+
# FROM modularization AS m2
|
|
343
|
+
# WHERE DATE(m1.date) = DATE(m2.date)
|
|
344
|
+
# )
|
|
345
|
+
# );
|
|
346
|
+
# SQL
|
|
324
347
|
|
|
325
348
|
# dataset.query(delete_query)
|
|
326
349
|
end
|
|
@@ -348,7 +371,7 @@ class GithubRepoStatistics
|
|
|
348
371
|
if HOTSPOT
|
|
349
372
|
hotspot_output = []
|
|
350
373
|
|
|
351
|
-
filter_files(file_team_map
|
|
374
|
+
filter_files(file_team_map:, size: 0).each do |line|
|
|
352
375
|
file = line.first
|
|
353
376
|
contributors = line.last.first
|
|
354
377
|
lines_of_code = count_lines_of_code(file)
|
|
@@ -388,4 +411,16 @@ class GithubRepoStatistics
|
|
|
388
411
|
@begin_time -= duration_in_days
|
|
389
412
|
contribution_message
|
|
390
413
|
end
|
|
414
|
+
|
|
415
|
+
def hotspot_check(files:, branch:)
|
|
416
|
+
duration_in_days = @duration_in_days.to_i
|
|
417
|
+
start_date = @begin_time.to_time.to_i - duration_in_days * 86_400
|
|
418
|
+
end_date = @begin_time.to_time.to_i
|
|
419
|
+
|
|
420
|
+
_, _, _, files_changed_by_many_teams, = analyze_changed_files(
|
|
421
|
+
uniq_code_files_with_changes: files, start_date:, end_date:, branch:
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
files_changed_by_many_teams
|
|
425
|
+
end
|
|
391
426
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'octokit'
|
|
2
4
|
require 'json'
|
|
3
5
|
require 'google/cloud/bigquery'
|
|
@@ -34,7 +36,7 @@ class ReleaseMergeReport
|
|
|
34
36
|
client.auto_paginate = true
|
|
35
37
|
|
|
36
38
|
tags = client.tags(@repo)
|
|
37
|
-
branch_info = Hash.new { |hash, key| hash[key] = { count: 0, teams: [], tribes: []} }
|
|
39
|
+
branch_info = Hash.new { |hash, key| hash[key] = { count: 0, teams: [], tribes: [] } }
|
|
38
40
|
|
|
39
41
|
tags.each do |tag|
|
|
40
42
|
next if !tag.name.match?(/^(v23|v24)\./) && !tag.name.match?(/^(23|24)\./)
|
|
@@ -47,7 +49,7 @@ class ReleaseMergeReport
|
|
|
47
49
|
|
|
48
50
|
# Count merged pull requests associated with the branch
|
|
49
51
|
pull_requests = client.pull_requests(@repo, state: 'closed', sort: 'updated', direction: 'desc', base: branch_name)
|
|
50
|
-
.select
|
|
52
|
+
.select(&:merged_at)
|
|
51
53
|
|
|
52
54
|
pull_requests.each do |pr|
|
|
53
55
|
branch_info[branch_name][:count] += 1
|
|
@@ -65,8 +67,8 @@ class ReleaseMergeReport
|
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
def group_branch_counts(branch_info)
|
|
68
|
-
patch_counts = Hash.new { |hash, key| hash[key] = { count: 0, teams: [], tribes: []} }
|
|
69
|
-
hotfix_counts = Hash.new { |hash, key| hash[key] = { count: 0, teams: [], tribes: []} }
|
|
70
|
+
patch_counts = Hash.new { |hash, key| hash[key] = { count: 0, teams: [], tribes: [] } }
|
|
71
|
+
hotfix_counts = Hash.new { |hash, key| hash[key] = { count: 0, teams: [], tribes: [] } }
|
|
70
72
|
|
|
71
73
|
branch_info.each do |branch, info|
|
|
72
74
|
major_minor_version, patch_version = branch.match(/^#{@branch_prefix}(\d+\.\d+)(?:\.(\d+))?/)&.captures
|
|
@@ -77,8 +79,7 @@ class ReleaseMergeReport
|
|
|
77
79
|
patch_counts[major_minor_version][:teams] += info[:teams]
|
|
78
80
|
patch_counts[major_minor_version][:tribes] += info[:tribes]
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
elsif info[:count] > 0
|
|
82
|
+
elsif (info[:count]).positive?
|
|
82
83
|
# Branch is a hotfix version
|
|
83
84
|
hotfix_counts[major_minor_version][:count] += info[:count]
|
|
84
85
|
hotfix_counts[major_minor_version][:teams] += info[:teams]
|
|
@@ -95,14 +96,14 @@ class ReleaseMergeReport
|
|
|
95
96
|
end
|
|
96
97
|
|
|
97
98
|
def export_to_bigquery(branch_counts)
|
|
98
|
-
require
|
|
99
|
-
require
|
|
99
|
+
require 'google/cloud/bigquery'
|
|
100
|
+
require 'json'
|
|
100
101
|
creds = JSON.parse(ENV['BQ_CREDENTIALS'])
|
|
101
102
|
bigquery = Google::Cloud::Bigquery.new(
|
|
102
|
-
project_id:
|
|
103
|
+
project_id: 'hellofresh-android',
|
|
103
104
|
credentials: creds
|
|
104
105
|
)
|
|
105
|
-
dataset = bigquery.dataset
|
|
106
|
+
dataset = bigquery.dataset 'github_data'
|
|
106
107
|
|
|
107
108
|
date = DateTime.now
|
|
108
109
|
|
|
@@ -113,18 +114,18 @@ class ReleaseMergeReport
|
|
|
113
114
|
|
|
114
115
|
# Construct the SQL query
|
|
115
116
|
query = <<~SQL
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
117
|
+
MERGE INTO release_merges AS target
|
|
118
|
+
USING (SELECT '#{branch}' AS release, '#{@repo}' AS platform) AS source
|
|
119
|
+
ON target.release = source.release AND target.platform = source.platform
|
|
120
|
+
WHEN MATCHED THEN
|
|
121
|
+
UPDATE SET
|
|
122
|
+
target.merge_count = #{count[:count]},
|
|
123
|
+
target.timestamp = '#{date}',
|
|
124
|
+
target.contributors = ARRAY[#{teams_json}],
|
|
125
|
+
target.contributors_tribe = ARRAY[#{tribes_json}]
|
|
126
|
+
WHEN NOT MATCHED THEN
|
|
127
|
+
INSERT (release, merge_count, platform, timestamp, contributors, contributors_tribe)
|
|
128
|
+
VALUES ('#{branch}', #{count[:count]}, '#{@repo}', '#{date}', ARRAY[#{teams_json}], ARRAY[#{tribes_json}]);
|
|
128
129
|
SQL
|
|
129
130
|
|
|
130
131
|
# Execute the query
|
|
@@ -141,17 +142,17 @@ class ReleaseMergeReport
|
|
|
141
142
|
|
|
142
143
|
# Construct the SQL query
|
|
143
144
|
query = <<~SQL
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
MERGE INTO release_merges AS target
|
|
146
|
+
USING (SELECT '#{branch}' AS release, '#{@repo}' AS platform) AS source
|
|
147
|
+
ON target.release = source.release AND target.platform = source.platform
|
|
148
|
+
WHEN MATCHED THEN
|
|
149
|
+
UPDATE SET
|
|
150
|
+
target.hotfix_count = #{count[:count]},
|
|
151
|
+
target.contributors_hotfixes = ARRAY[#{teams_json}],
|
|
152
|
+
target.contributors_hotfixes_tribe = ARRAY[#{tribes_json}]
|
|
153
|
+
WHEN NOT MATCHED THEN
|
|
154
|
+
INSERT (release, hotfix_count, platform, contributors_hotfixes, contributors_hotfixes_tribe )
|
|
155
|
+
VALUES ('#{branch}', #{count[:count]}, '#{@repo}', ARRAY[#{teams_json}], ARRAY[#{tribes_json}]);
|
|
155
156
|
SQL
|
|
156
157
|
|
|
157
158
|
# Execute the query
|
|
@@ -90,30 +90,30 @@ class ReviewReport
|
|
|
90
90
|
# ENV['BQ_CREDENTIALS'] = `cat /Users/serghei.moret/.config/gcloud/application_default_credentials.json`
|
|
91
91
|
|
|
92
92
|
if ENV['BQ_CREDENTIALS']
|
|
93
|
-
require
|
|
94
|
-
require
|
|
93
|
+
require 'google/cloud/bigquery'
|
|
94
|
+
require 'json'
|
|
95
95
|
creds = JSON.parse(ENV['BQ_CREDENTIALS'])
|
|
96
96
|
bigquery = Google::Cloud::Bigquery.new(
|
|
97
|
-
project_id:
|
|
97
|
+
project_id: 'hellofresh-android',
|
|
98
98
|
credentials: creds
|
|
99
99
|
)
|
|
100
|
-
dataset = bigquery.dataset
|
|
100
|
+
dataset = bigquery.dataset 'github_data'
|
|
101
101
|
|
|
102
102
|
query = <<~SQL
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
103
|
+
MERGE INTO pr_reviews AS target
|
|
104
|
+
USING (SELECT '#{week}' AS calendar_week, '#{@repo}' AS platform) AS source
|
|
105
|
+
ON target.calendar_week = source.calendar_week AND target.platform = source.platform
|
|
106
|
+
WHEN MATCHED THEN
|
|
107
|
+
UPDATE SET
|
|
108
|
+
target.change_requested_reviews = #{change_requested_reviews},
|
|
109
|
+
target.reviews_with_comments = #{reviews_with_comments},
|
|
110
|
+
target.total_reviews = #{total_reviews},
|
|
111
|
+
target.average_review_time_hours = #{average_time_hours.round(2)},
|
|
112
|
+
target.total_prs = #{total_count},
|
|
113
|
+
target.platform = '#{@repo}'
|
|
114
|
+
WHEN NOT MATCHED THEN
|
|
115
|
+
INSERT (calendar_week, total_prs, average_review_time_hours, total_reviews, reviews_with_comments, change_requested_reviews, platform)
|
|
116
|
+
VALUES ('#{week}', #{total_count}, #{average_time_hours.round(2)}, #{total_reviews}, #{reviews_with_comments}, #{change_requested_reviews}, '#{@repo}');
|
|
117
117
|
SQL
|
|
118
118
|
|
|
119
119
|
dataset.query(query)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: github_repo_statistics
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.3.
|
|
4
|
+
version: 2.3.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Serghei Moret
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-05
|
|
11
|
+
date: 2024-06-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: date
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: faraday-retry
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
@@ -39,7 +39,7 @@ dependencies:
|
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: google-cloud-bigquery
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
@@ -53,7 +53,7 @@ dependencies:
|
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
56
|
+
name: octokit
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
59
|
- - ">="
|
|
@@ -67,7 +67,7 @@ dependencies:
|
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
70
|
+
name: pry
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
73
|
- - ">="
|