github_repo_statistics 2.0.8 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +67 -0
- data/Gemfile.lock +19 -1
- data/README.md +99 -31
- data/bin/force_merge_report +61 -0
- data/bin/review_report +58 -0
- data/github_repo_statistics.gemspec +2 -0
- data/lib/github_repo_statistics/force_merge_report.rb +87 -0
- data/lib/github_repo_statistics/github_repo_statistics.rb +7 -3
- data/lib/github_repo_statistics/review_report.rb +94 -0
- data/lib/github_repo_statistics/version.rb +1 -1
- data/spec/github_repo_statistics.rb +1 -1
- metadata +41 -3
- /data/{.github → spec/fixtures}/CODEOWNERS +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7625d04f395885b90b8f2faad896127df5e16604818d095f3e5455bf49d9c899
|
4
|
+
data.tar.gz: e6fec8b96326f6aff20e8ba63ccde41d3605381a692df51a3e7dceac8f0b218c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c943a3541d786ca121518c4545a0d82f036d71492ad83bec46ffffd187e4f6536475688acd1f3841e20ded27b29eb4759675e40ef86d55a66ef2ae147487e8fe
|
7
|
+
data.tar.gz: 76c00e2a9cc37a21026cb31229f6c7c71733428b1fc1d06fd24f5c8e9d5c8777d85d23e45c4af73ddaf699d6c8afd63dab38dc40af2780c0f73f704e32b0b4b2
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2024-02-27 08:25:07 UTC using RuboCop version 1.60.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 11
|
10
|
+
# Configuration parameters: AllowedMethods.
|
11
|
+
# AllowedMethods: enums
|
12
|
+
Lint/ConstantDefinitionInBlock:
|
13
|
+
Exclude:
|
14
|
+
- 'spec/github_repo_statistics.rb'
|
15
|
+
|
16
|
+
# Offense count: 8
|
17
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
18
|
+
Metrics/AbcSize:
|
19
|
+
Max: 96
|
20
|
+
|
21
|
+
# Offense count: 7
|
22
|
+
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
23
|
+
# AllowedMethods: refine
|
24
|
+
Metrics/BlockLength:
|
25
|
+
Max: 73
|
26
|
+
|
27
|
+
# Offense count: 1
|
28
|
+
# Configuration parameters: CountComments, CountAsOne.
|
29
|
+
Metrics/ClassLength:
|
30
|
+
Max: 229
|
31
|
+
|
32
|
+
# Offense count: 4
|
33
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
34
|
+
Metrics/CyclomaticComplexity:
|
35
|
+
Max: 18
|
36
|
+
|
37
|
+
# Offense count: 8
|
38
|
+
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
39
|
+
Metrics/MethodLength:
|
40
|
+
Max: 62
|
41
|
+
|
42
|
+
# Offense count: 4
|
43
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
44
|
+
Metrics/PerceivedComplexity:
|
45
|
+
Max: 18
|
46
|
+
|
47
|
+
# Offense count: 3
|
48
|
+
# Configuration parameters: AllowedConstants.
|
49
|
+
Style/Documentation:
|
50
|
+
Exclude:
|
51
|
+
- 'spec/**/*'
|
52
|
+
- 'test/**/*'
|
53
|
+
- 'lib/github_repo_statistics/force_merge_report.rb'
|
54
|
+
- 'lib/github_repo_statistics/github_repo_statistics.rb'
|
55
|
+
- 'lib/github_repo_statistics/review_report.rb'
|
56
|
+
|
57
|
+
# Offense count: 1
|
58
|
+
Style/MultilineBlockChain:
|
59
|
+
Exclude:
|
60
|
+
- 'lib/github_repo_statistics/github_repo_statistics.rb'
|
61
|
+
|
62
|
+
# Offense count: 13
|
63
|
+
# This cop supports safe autocorrection (--autocorrect).
|
64
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
|
65
|
+
# URISchemes: http, https
|
66
|
+
Layout/LineLength:
|
67
|
+
Max: 229
|
data/Gemfile.lock
CHANGED
@@ -1,20 +1,33 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
github_repo_statistics (2.0
|
4
|
+
github_repo_statistics (2.1.0)
|
5
5
|
date
|
6
|
+
faraday-retry
|
7
|
+
octokit
|
6
8
|
pry
|
7
9
|
|
8
10
|
GEM
|
9
11
|
remote: https://rubygems.org/
|
10
12
|
specs:
|
13
|
+
addressable (2.8.4)
|
14
|
+
public_suffix (>= 2.0.2, < 6.0)
|
11
15
|
ast (2.4.2)
|
12
16
|
coderay (1.1.3)
|
13
17
|
date (3.3.4)
|
14
18
|
diff-lcs (1.5.0)
|
19
|
+
faraday (2.7.9)
|
20
|
+
faraday-net_http (>= 2.0, < 3.1)
|
21
|
+
ruby2_keywords (>= 0.0.4)
|
22
|
+
faraday-net_http (3.0.2)
|
23
|
+
faraday-retry (2.2.0)
|
24
|
+
faraday (~> 2.0)
|
15
25
|
json (2.7.1)
|
16
26
|
language_server-protocol (3.17.0.3)
|
17
27
|
method_source (1.0.0)
|
28
|
+
octokit (8.0.0)
|
29
|
+
faraday (>= 1, < 3)
|
30
|
+
sawyer (~> 0.9)
|
18
31
|
parallel (1.24.0)
|
19
32
|
parser (3.3.0.4)
|
20
33
|
ast (~> 2.4.1)
|
@@ -22,6 +35,7 @@ GEM
|
|
22
35
|
pry (0.14.2)
|
23
36
|
coderay (~> 1.1)
|
24
37
|
method_source (~> 1.0)
|
38
|
+
public_suffix (5.0.1)
|
25
39
|
racc (1.7.3)
|
26
40
|
rainbow (3.1.1)
|
27
41
|
rake (13.1.0)
|
@@ -54,6 +68,10 @@ GEM
|
|
54
68
|
rubocop-ast (1.30.0)
|
55
69
|
parser (>= 3.2.1.0)
|
56
70
|
ruby-progressbar (1.13.0)
|
71
|
+
ruby2_keywords (0.0.5)
|
72
|
+
sawyer (0.9.2)
|
73
|
+
addressable (>= 2.3.5)
|
74
|
+
faraday (>= 0.17.3, < 3)
|
57
75
|
unicode-display_width (2.5.0)
|
58
76
|
|
59
77
|
PLATFORMS
|
data/README.md
CHANGED
@@ -1,39 +1,107 @@
|
|
1
1
|
# GithubRepoStatistics
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/github_repo_statistics`. To experiment with that code, run `bin/console` for an interactive prompt.
|
3
|
+
This is a standalone ruby executable that allows you to collect statistics
|
6
4
|
|
7
5
|
## Installation
|
6
|
+
*Prerequisite:* You will need at least Ruby 3.1.0 to run this gem. You can install it throug `rbenv` or `rvm`. (e.g. `brew install rvm && rvm install 3.2.2 && rvm use defaukt 3.2.2`)
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
Install the gem and add to the application's Gemfile by executing:
|
12
|
-
|
13
|
-
$ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
|
14
|
-
|
15
|
-
If bundler is not being used to manage dependencies, install the gem by executing:
|
16
|
-
|
17
|
-
$ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
|
8
|
+
*Installing the gem:*
|
9
|
+
`gem install github_repo_statistics`
|
18
10
|
|
19
11
|
## Usage
|
20
12
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
13
|
+
```
|
14
|
+
github_repo_statistics -h
|
15
|
+
Usage: github_repo_statistics [options]
|
16
|
+
--debug Enable debug mode
|
17
|
+
--ci Do not print the info messages for better CI text parsing [default: false]
|
18
|
+
--codeowners Print CODEOWNERS info [default: false]
|
19
|
+
--hotspot-files Print the found hotspot files (big files touched by many) [default: false]
|
20
|
+
--excluded-contributors STRING
|
21
|
+
Comma-delimited list of excluded contributors [example: WEB,RAILS,MOBILE]
|
22
|
+
--excluded-files STRING Comma-delimited list of excluded keywords [example: ViewController,AppDelegate.swift]
|
23
|
+
--steps STRING Number of steps the script will go into the past [default: 1]
|
24
|
+
--duration-in-days STRING Number of days to aggregate the changes for [default: 30]
|
25
|
+
--path STRING Path to the directory or file to calculate the ownership [default: "."]
|
26
|
+
--team-regex STRING Regex that will identify the team name [default: "[A-Za-z]+"]
|
27
|
+
--codeowners-path STRING Path to CODEOWNERS file [default: .github/CODEOWNERS]
|
28
|
+
--big-file-size STRING The amount of lines in the file to be considered big [default: 250]
|
29
|
+
--default-branch STRING The default branch to pull and run metrics for [default: master]
|
30
|
+
--code-extensions STRING The file extensions that consider to be code [default: ".kt,.swift"]
|
31
|
+
--output-to-files Puts the output for hotspot and codeowners into files instead of the STDOUT (useful for CI and big amount of data) [default:false]
|
32
|
+
-v, --version Display the version of the gem
|
33
|
+
-h, --help Display this help message
|
34
|
+
|
35
|
+
Examples of usage:
|
36
|
+
github_repo_statistics --path src/test --exclusions WEB,RAILS --steps 2 --duration-in-days 90 --hotspot-files --debug
|
37
|
+
|
38
|
+
|
39
|
+
*Timeframe:* 2023-11-01 to 2024-01-30
|
40
|
+
*Code files with a single contributor:* 94.4%
|
41
|
+
*Existing files changed by many teams:* 101
|
42
|
+
*Current existing [".swift"] files:* 1805
|
43
|
+
*Cross-Squad Dependency:*
|
44
|
+
*Contributions by multiple squads to the same files:* 282
|
45
|
+
*Contributions by single squads contributing to single files:* 501
|
46
|
+
*Hotspot Code Changes:* 42.48%
|
47
|
+
*Churn count(commits to files by multiple teams):* 701
|
48
|
+
*Total amount of commits:* 1650
|
49
|
+
*Total lines of hotspot code:* 19965
|
50
|
+
*[".swift"] files with multiple contributors:* 101
|
51
|
+
*[".swift"] files exceeding 250 lines with multiple contributors:* 37
|
52
|
+
*Total amount of commits to [".swift"] files:* 1650
|
53
|
+
*Total [".swift"] files changed:* 602
|
54
|
+
*Current total number of code files longer than 250 lines:* 102
|
55
|
+
*Current total of [".swift"] files in the folder:* 1805
|
56
|
+
*Contributors:* {"CCE"=>245, "SQDREACT"=>145, "DSAT"=>124, "PROD"=>111, "ORGANIC"=>92, "CS"=>75, "SF"=>61, "PCON"=>55, "ER"=>48, "VIRRCH"=>45, "PACC"=>37, "MN"=>31, "CAWLAPP"=>28, "CMT"=>25, "PAYS"=>24, "EX"=>12, "REF"=>11, "ZEST"=>3, "UFI"=>2, "VRIRRCH"=>2, "CHANGED"=>1, "CLEANUPS"=>1, "DPLAN"=>1}
|
57
|
+
```
|
58
|
+
|
59
|
+
```
|
60
|
+
force_merge_report --help
|
61
|
+
Usage: github_repo_statistics [options]
|
62
|
+
--ci Do not print the info messages for better CI text parsing [default: false]
|
63
|
+
--default-branch STRING The default branch to pull and run metrics for [default: master]
|
64
|
+
--github-token STRING GitHub API token [default: ""]
|
65
|
+
--duration-in-days STRING Number of days to aggregate the changes for [default: 90]
|
66
|
+
--github-repo STRING GitHub repository name and owner (e.g. octokit/octokit) [default: ""]
|
67
|
+
-v, --version Display the version of the gem
|
68
|
+
-h, --help Display this help message
|
69
|
+
|
70
|
+
Examples of usage:
|
71
|
+
force_merge_report --github-token ghp_SAKLGSJHG8asgiasKASGkhsa --github-repo octokit/octokit --duration-in-days 7
|
72
|
+
|
73
|
+
PR #12315 - Merged at: 2024-02-26 16:20:22 UTC
|
74
|
+
Failed Checks:
|
75
|
+
- Workflow: Mergeable: Size check, Conclusion: failure
|
76
|
+
---
|
77
|
+
|
78
|
+
PR #312455 - Merged at: 2024-02-26 15:01:09 UTC
|
79
|
+
Failed Checks:
|
80
|
+
- Workflow: Mergeable: PR Review, Conclusion: failure
|
81
|
+
---
|
82
|
+
|
83
|
+
Weekly Summary:
|
84
|
+
Calendar week 2022-05: Total PRs: 11, Force-merged PRs: 2
|
85
|
+
Failed Workflows:
|
86
|
+
- Mergeable: Size check: 1
|
87
|
+
- Mergeable: PR Review: 1
|
88
|
+
```
|
89
|
+
|
90
|
+
```
|
91
|
+
review_report -h
|
92
|
+
Usage: github_repo_statistics [options]
|
93
|
+
--duration-in-days STRING Number of days to aggregate the changes for [default: 90]
|
94
|
+
--default-branch STRING The default branch to pull and run metrics for [default: master]
|
95
|
+
--github-token STRING GitHub API token [default: ""]
|
96
|
+
--github-repo STRING GitHub repository name and owner (e.g. octokit/octokit) [default: ""]
|
97
|
+
-v, --version Display the version of the gem
|
98
|
+
-h, --help Display this help message
|
99
|
+
|
100
|
+
Examples of usage:
|
101
|
+
review_report --github-token ghp_SAKLGSJHG8asgiasKASGkhsa --github-repo octokit/octokit
|
102
|
+
|
103
|
+
Calendar week 2023-04: Average time taken for 24 pull requests is 2.49 hours.
|
104
|
+
Total reviews: 440
|
105
|
+
Reviews with comments: 247
|
106
|
+
Change requested reviews: 7
|
107
|
+
```
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'date'
|
5
|
+
require 'optparse'
|
6
|
+
require 'pry'
|
7
|
+
require_relative '../lib/github_repo_statistics/version'
|
8
|
+
require_relative '../lib/github_repo_statistics/force_merge_report'
|
9
|
+
|
10
|
+
options = {}
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = 'Usage: github_repo_statistics [options]'
|
13
|
+
|
14
|
+
opts.on('--ci', 'Do not print the info messages for better CI text parsing [default: false]') do
|
15
|
+
options[:ci] = true
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on('--default-branch STRING',
|
19
|
+
'The default branch to pull and run metrics for [default: master]') do |default_branch|
|
20
|
+
options[:default_branch] = default_branch
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on('--github-token STRING',
|
24
|
+
'GitHub API token [default: ""]') do |github_token|
|
25
|
+
options[:github_token] = github_token
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on('--duration-in-days STRING',
|
29
|
+
'Number of days to aggregate the changes for [default: 90]') do |duration_in_days|
|
30
|
+
options[:duration_in_days] = duration_in_days.to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('--github-repo STRING',
|
34
|
+
'GitHub repository name and owner (e.g. octokit/octokit) [default: ""]') do |github_repo|
|
35
|
+
options[:github_repo] = github_repo
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on('-v', '--version', 'Display the version of the gem') do
|
39
|
+
puts "github_repo_statistics version #{GithubRepoStatistics::VERSION}"
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on('-h', '--help', 'Display this help message') do
|
44
|
+
puts opts
|
45
|
+
puts <<~EXAMPLES
|
46
|
+
|
47
|
+
Examples:
|
48
|
+
force_merge_report --github-token ghp_bKasj29ndkfdksf3rjt2ngi43j --github-repo octokit/octokit
|
49
|
+
EXAMPLES
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
end.parse!
|
53
|
+
|
54
|
+
DEFAULT_BRANCH = options[:default_branch] || 'master'
|
55
|
+
|
56
|
+
raise 'Please provide GitHub token using --github-token flag' if options[:github_token].nil?
|
57
|
+
raise 'Please provide GitHub repo name using --github-repo flag' if options[:github_repo].nil?
|
58
|
+
raise 'Please provide default GitHub branch using --default-branch flag' if DEFAULT_BRANCH.nil?
|
59
|
+
|
60
|
+
ForceMergeReport.new(token: options[:github_token], repo: options[:github_repo],
|
61
|
+
branch: DEFAULT_BRANCH, duration: options[:duration_in_days] || 30, ci: options[:ci]).report
|
data/bin/review_report
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'date'
|
5
|
+
require 'optparse'
|
6
|
+
require 'pry'
|
7
|
+
require_relative '../lib/github_repo_statistics/version'
|
8
|
+
require_relative '../lib/github_repo_statistics/force_merge_report'
|
9
|
+
require_relative '../lib/github_repo_statistics/review_report'
|
10
|
+
|
11
|
+
options = {}
|
12
|
+
OptionParser.new do |opts|
|
13
|
+
opts.banner = 'Usage: github_repo_statistics [options]'
|
14
|
+
|
15
|
+
opts.on('--duration-in-days STRING',
|
16
|
+
'Number of days to aggregate the changes for [default: 90]') do |duration_in_days|
|
17
|
+
options[:duration_in_days] = duration_in_days.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on('--default-branch STRING',
|
21
|
+
'The default branch to pull and run metrics for [default: master]') do |default_branch|
|
22
|
+
options[:default_branch] = default_branch
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on('--github-token STRING',
|
26
|
+
'GitHub API token [default: ""]') do |github_token|
|
27
|
+
options[:github_token] = github_token
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on('--github-repo STRING',
|
31
|
+
'GitHub repository name and owner (e.g. octokit/octokit) [default: ""]') do |github_repo|
|
32
|
+
options[:github_repo] = github_repo
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on('-v', '--version', 'Display the version of the gem') do
|
36
|
+
puts "github_repo_statistics version #{GithubRepoStatistics::VERSION}"
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on('-h', '--help', 'Display this help message') do
|
41
|
+
puts opts
|
42
|
+
puts <<~EXAMPLES
|
43
|
+
|
44
|
+
Examples:
|
45
|
+
review_report --github-token ghp_bKasj29ndkfdksf3rjt2ngi43j --github-repo octokit/octokit
|
46
|
+
EXAMPLES
|
47
|
+
exit
|
48
|
+
end
|
49
|
+
end.parse!
|
50
|
+
|
51
|
+
DEFAULT_BRANCH = options[:default_branch] || 'master'
|
52
|
+
|
53
|
+
raise 'Please provide GitHub token using --github-token flag' if options[:github_token].nil?
|
54
|
+
raise 'Please provide GitHub repo name using --github-repo flag' if options[:github_repo].nil?
|
55
|
+
raise 'Please provide default GitHub branch using --default-branch flag' if DEFAULT_BRANCH.nil?
|
56
|
+
|
57
|
+
ReviewReport.new(token: options[:github_token], repo: options[:github_repo], branch: DEFAULT_BRANCH,
|
58
|
+
duration: options[:duration_in_days] || 30).report
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'octokit'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
class ForceMergeReport
|
7
|
+
def initialize(token:, repo:, branch:, duration:, ci:)
|
8
|
+
@token = token
|
9
|
+
@repo = repo
|
10
|
+
@branch = branch
|
11
|
+
@duration_in_days = duration
|
12
|
+
@ci = ci
|
13
|
+
end
|
14
|
+
|
15
|
+
def report
|
16
|
+
client = Octokit::Client.new(access_token: @token)
|
17
|
+
client.auto_paginate = true
|
18
|
+
|
19
|
+
# Get all pull requests created after the target date
|
20
|
+
pull_requests = client.list_issues(@repo, state: 'closed', since: DateTime.now - @duration_in_days)
|
21
|
+
|
22
|
+
# Sort PRs into monthly chunks
|
23
|
+
weekly_pull_requests = pull_requests.group_by { |pr| pr.closed_at.strftime('%Y-%U') }
|
24
|
+
|
25
|
+
# Initialize a hash to store monthly summaries
|
26
|
+
weekly_summaries = Hash.new { |hash, key| hash[key] = { total: 0, failed: 0, workflows: Hash.new(0) } }
|
27
|
+
|
28
|
+
weeks = @duration_in_days / 7
|
29
|
+
|
30
|
+
(weekly_pull_requests.keys.sort[-weeks..] || []).each do |month|
|
31
|
+
pull_requests_for_month = weekly_pull_requests[month]
|
32
|
+
# Iterate through pull requests for the current month
|
33
|
+
pull_requests_for_month.each do |issue|
|
34
|
+
# Get the pull request details
|
35
|
+
pr = client.pull_request(@repo, issue.number)
|
36
|
+
|
37
|
+
# Check if the pull request is merged
|
38
|
+
next unless pr.merged_at
|
39
|
+
|
40
|
+
# Get the check runs for the pull request's head commit
|
41
|
+
check_runs = client.check_runs_for_ref(@repo, pr.head.sha)
|
42
|
+
latest_check_runs = check_runs.check_runs.uniq(&:name)
|
43
|
+
|
44
|
+
statuses = client.statuses(@repo, pr.head.sha)
|
45
|
+
latest_statuses = statuses.uniq(&:context)
|
46
|
+
|
47
|
+
all_checks = latest_check_runs + latest_statuses
|
48
|
+
|
49
|
+
# Filter checks without meeting the required status checks
|
50
|
+
failed_checks = all_checks.reject { |check| check.conclusion == 'success' || check.state == 'success' }
|
51
|
+
|
52
|
+
# Update monthly summary
|
53
|
+
weekly_summaries[month][:total] += 1
|
54
|
+
weekly_summaries[month][:failed] += 1 unless failed_checks.empty?
|
55
|
+
|
56
|
+
# Update workflow counts
|
57
|
+
failed_checks.each do |check|
|
58
|
+
workflow_name = check.name || check.context
|
59
|
+
weekly_summaries[month][:workflows][workflow_name] += 1
|
60
|
+
end
|
61
|
+
|
62
|
+
# Print details of merged pull requests without meeting the required criteria for the last 6 months
|
63
|
+
next if failed_checks.empty?
|
64
|
+
|
65
|
+
unless @ci
|
66
|
+
puts "PR ##{pr.number} - Merged at: #{pr.merged_at}"
|
67
|
+
puts 'Failed Checks:'
|
68
|
+
failed_checks.each do |check|
|
69
|
+
puts "- Workflow: #{check.name || check.context}, Conclusion: #{check.conclusion || check.state}"
|
70
|
+
end
|
71
|
+
puts '---'
|
72
|
+
puts
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Print the weekly summary
|
78
|
+
puts 'Weekly Summary:'
|
79
|
+
weekly_summaries.each do |month, summary|
|
80
|
+
puts "Calendar week #{month}: Total PRs: #{summary[:total]}, Force-merged PRs: #{summary[:failed]}"
|
81
|
+
puts 'Failed Workflows:'
|
82
|
+
summary[:workflows].each do |workflow, count|
|
83
|
+
puts " - #{workflow}: #{count}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -205,7 +205,8 @@ class GithubRepoStatistics
|
|
205
205
|
|
206
206
|
puts "\n#{filename} [#{commit_count}]:#{teams}\n" if @debug
|
207
207
|
end
|
208
|
-
[all_teams, cross_teams_count, single_ownership_teams_count, files_changed_by_many_teams, total_changes,
|
208
|
+
[all_teams, cross_teams_count, single_ownership_teams_count, files_changed_by_many_teams, total_changes,
|
209
|
+
file_team_map]
|
209
210
|
end
|
210
211
|
|
211
212
|
def filter_files(file_team_map:)
|
@@ -226,7 +227,9 @@ class GithubRepoStatistics
|
|
226
227
|
all_files_with_changes = files_with_changes(directory_path: @directory_path, start_date:, end_date:).split.sort
|
227
228
|
code_files_with_changes = filter_existing_code_files(all_files_with_changes)
|
228
229
|
uniq_code_files_with_changes = code_files_with_changes.uniq
|
229
|
-
all_teams, cross_teams_count, single_ownership_teams_count, files_changed_by_many_teams, total_changes, file_team_map = analyze_changed_files(
|
230
|
+
all_teams, cross_teams_count, single_ownership_teams_count, files_changed_by_many_teams, total_changes, file_team_map = analyze_changed_files(
|
231
|
+
uniq_code_files_with_changes:, start_date:, end_date:
|
232
|
+
)
|
230
233
|
occurrences = all_teams.flatten.compact.tally
|
231
234
|
sorted_occurrences = occurrences.sort_by { |element, count| [-count, element] }
|
232
235
|
contributors = Hash[sorted_occurrences]
|
@@ -264,7 +267,8 @@ class GithubRepoStatistics
|
|
264
267
|
file = line.first
|
265
268
|
contributors = line.last.first
|
266
269
|
commits = line.last.last
|
267
|
-
hotspot_output += " #{file.gsub(@directory_path,
|
270
|
+
hotspot_output += " #{file.gsub(@directory_path,
|
271
|
+
'')} Contributors: #{contributors} Commits: #{commits} Owner: #{find_owner(file:)}\n"
|
268
272
|
end
|
269
273
|
|
270
274
|
if FILE_OUTPUT
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'octokit'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
class ReviewReport
|
7
|
+
def initialize(token:, repo:, branch:, duration:)
|
8
|
+
@token = token
|
9
|
+
@repo = repo
|
10
|
+
@branch = branch
|
11
|
+
@duration_in_days = duration
|
12
|
+
end
|
13
|
+
|
14
|
+
def percentile(array, percentile)
|
15
|
+
sorted_array = array.sort
|
16
|
+
k = (percentile * (sorted_array.length - 1)).floor
|
17
|
+
l = k + 1
|
18
|
+
q1 = sorted_array[k]
|
19
|
+
q2 = sorted_array[l]
|
20
|
+
q1 + (q2 - q1) * (percentile * (sorted_array.length - 1) - k)
|
21
|
+
end
|
22
|
+
|
23
|
+
def report
|
24
|
+
client = Octokit::Client.new(access_token: @token)
|
25
|
+
client.auto_paginate = true
|
26
|
+
|
27
|
+
# Fetch pull requests created after the target date
|
28
|
+
pull_requests = client.list_issues(@repo, state: 'closed', since: DateTime.now - @duration_in_days)
|
29
|
+
|
30
|
+
# Group pull requests by week
|
31
|
+
pull_requests_by_week = pull_requests.group_by { |pr| pr[:closed_at].strftime('%Y-%U') }
|
32
|
+
|
33
|
+
weeks = @duration_in_days / 7
|
34
|
+
|
35
|
+
# Iterate over months within the days duration limit
|
36
|
+
pull_requests_by_week.keys.sort[-weeks..].each do |week|
|
37
|
+
total_time_seconds = 0
|
38
|
+
total_count = 0
|
39
|
+
total_count_calc = 0
|
40
|
+
total_reviews = 0
|
41
|
+
reviews_with_comments = 0
|
42
|
+
change_requested_reviews = 0
|
43
|
+
|
44
|
+
# Iterate over pull requests in the week
|
45
|
+
pull_requests_by_week[week].each do |issue|
|
46
|
+
pull_request = client.pull_request(@repo, issue.number)
|
47
|
+
last_commit_sha = pull_request[:head][:sha]
|
48
|
+
last_commit = client.commit(@repo, last_commit_sha)
|
49
|
+
|
50
|
+
reviews = []
|
51
|
+
review_page = 1
|
52
|
+
loop do
|
53
|
+
response = client.pull_request_reviews(@repo, pull_request[:number], per_page: 100,
|
54
|
+
page: review_page)
|
55
|
+
break if response.empty?
|
56
|
+
|
57
|
+
reviews.concat(response)
|
58
|
+
review_page += 1
|
59
|
+
end
|
60
|
+
|
61
|
+
total_reviews += reviews.size
|
62
|
+
|
63
|
+
next unless reviews.any?
|
64
|
+
|
65
|
+
last_review = reviews.max_by { |review| review[:submitted_at] }
|
66
|
+
time_taken_seconds = (last_review[:submitted_at] - last_commit[:commit][:author][:date]).abs
|
67
|
+
|
68
|
+
if time_taken_seconds / 3600.0 < 8 && time_taken_seconds / 3600.0 > 0.3
|
69
|
+
total_time_seconds += time_taken_seconds
|
70
|
+
total_count_calc += 1
|
71
|
+
end
|
72
|
+
total_count += 1
|
73
|
+
# Count reviews with comments
|
74
|
+
reviews_with_comments += reviews.count do |review|
|
75
|
+
comments = client.pull_request_review_comments(@repo, pull_request[:number], review[:id])
|
76
|
+
comments.any?
|
77
|
+
end
|
78
|
+
|
79
|
+
# Count reviews with "change_requested" state
|
80
|
+
change_requested_reviews += reviews.count { |review| review[:state] == 'CHANGES_REQUESTED' }
|
81
|
+
end
|
82
|
+
|
83
|
+
if total_count.positive?
|
84
|
+
average_time_hours = total_time_seconds / total_count_calc / 3600.0 # Convert seconds to hours
|
85
|
+
puts "Calendar week #{week}: Average time taken for #{total_count} pull requests is #{average_time_hours.round(2)} hours."
|
86
|
+
puts " Total reviews: #{total_reviews}"
|
87
|
+
puts " Reviews with comments: #{reviews_with_comments}"
|
88
|
+
puts " Change requested reviews: #{change_requested_reviews}"
|
89
|
+
else
|
90
|
+
puts "Month #{week}: No pull requests with reviews."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -5,7 +5,7 @@ require_relative '../lib/github_repo_statistics/github_repo_statistics'
|
|
5
5
|
RSpec.describe GithubRepoStatistics do
|
6
6
|
EXCLUSIONS = %w[exclusion1 exclusion2].freeze
|
7
7
|
TEAM_REGEX = '[A-Za-z]+'
|
8
|
-
CODEOWNERS_PATH = '
|
8
|
+
CODEOWNERS_PATH = 'spec/fixtures/CODEOWNERS'
|
9
9
|
BIG_FILE_SIZE = 250
|
10
10
|
CI = false
|
11
11
|
DEFAULT_BRANCH = 'main'
|
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.0
|
4
|
+
version: 2.1.0
|
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-02-
|
11
|
+
date: 2024-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: date
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: octokit
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: pry
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,20 +52,38 @@ dependencies:
|
|
38
52
|
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: faraday-retry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
description:
|
42
70
|
email:
|
43
71
|
- serghei.moret@hellofresh.com
|
44
72
|
executables:
|
45
73
|
- console
|
74
|
+
- force_merge_report
|
46
75
|
- github_repo_statistics
|
76
|
+
- review_report
|
47
77
|
- setup
|
48
78
|
extensions: []
|
49
79
|
extra_rdoc_files: []
|
50
80
|
files:
|
51
|
-
- ".github/CODEOWNERS"
|
52
81
|
- ".github/workflows/hf_orc-gating.yaml"
|
53
82
|
- ".github/workflows/hf_validate-repository-ownership.yaml"
|
54
83
|
- ".github/workflows/hf_validate-tribe-squad-labels.yaml"
|
84
|
+
- ".gitignore"
|
85
|
+
- ".rubocop.yml"
|
86
|
+
- ".rubocop_todo.yml"
|
55
87
|
- CHANGELOG.md
|
56
88
|
- CODE_OF_CONDUCT.md
|
57
89
|
- Gemfile
|
@@ -60,13 +92,18 @@ files:
|
|
60
92
|
- README.md
|
61
93
|
- Rakefile
|
62
94
|
- bin/console
|
95
|
+
- bin/force_merge_report
|
63
96
|
- bin/github_repo_statistics
|
97
|
+
- bin/review_report
|
64
98
|
- bin/setup
|
65
99
|
- github_repo_statistics.gemspec
|
66
100
|
- lib/github_repo_statistics.rb
|
101
|
+
- lib/github_repo_statistics/force_merge_report.rb
|
67
102
|
- lib/github_repo_statistics/github_repo_statistics.rb
|
103
|
+
- lib/github_repo_statistics/review_report.rb
|
68
104
|
- lib/github_repo_statistics/version.rb
|
69
105
|
- sig/github_repo_statistics.rbs
|
106
|
+
- spec/fixtures/CODEOWNERS
|
70
107
|
- spec/fixtures/file.wrong
|
71
108
|
- spec/fixtures/file1.swift
|
72
109
|
- spec/fixtures/file2.kt
|
@@ -102,6 +139,7 @@ signing_key:
|
|
102
139
|
specification_version: 4
|
103
140
|
summary: This gem prints git ownership insights
|
104
141
|
test_files:
|
142
|
+
- spec/fixtures/CODEOWNERS
|
105
143
|
- spec/fixtures/file.wrong
|
106
144
|
- spec/fixtures/file1.swift
|
107
145
|
- spec/fixtures/file2.kt
|
File without changes
|