dev_metrics 0.1.0
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 +7 -0
- data/lib/dev_metrics/client.rb +124 -0
- data/lib/dev_metrics/markdown.rb +21 -0
- data/lib/dev_metrics.rb +29 -0
- metadata +45 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8f7562e3289d9e63952c28d3204862e9096ab36655b86c3feb59d150c6305451
|
4
|
+
data.tar.gz: '0329ed36f933b308443a701ef1407d136f090ffabc5ca8a87af65f94355ce05c'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 345d1aa1578ad62febbd626899f13e3fe66ac66b9d879d3bc71514f399bbb9239b5e8202b7d30abb0142686efe5f25a0ae5917c9d805920ec92c6793820e5cfa
|
7
|
+
data.tar.gz: b747b37e4fdac8801d4f6758e5ccadeced27c810bda5e643fd0e238af6ed9880bc1e02129446d545a88e507c4e0ab8238e2e735007e5ded3d34683f0e642e511
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'date'
|
4
|
+
require 'json'
|
5
|
+
require 'time'
|
6
|
+
require 'pry'
|
7
|
+
|
8
|
+
module DevMetrics
|
9
|
+
class Client
|
10
|
+
GITHUB_GRAPHQL_API = 'https://api.github.com/graphql'.freeze
|
11
|
+
ACCESS_TOKEN = ENV.fetch('GITHUB_ACCESS_TOKEN', nil)
|
12
|
+
|
13
|
+
def initialize(config)
|
14
|
+
@repo_name = config.repo_name
|
15
|
+
@bot_accounts = config.bot_accounts || []
|
16
|
+
@access_token = config.access_token || ENV.fetch('GITHUB_ACCESS_TOKEN', nil)
|
17
|
+
end
|
18
|
+
|
19
|
+
def process(period: Date.today)
|
20
|
+
uri = URI.parse(GITHUB_GRAPHQL_API)
|
21
|
+
request = build_request(uri, period)
|
22
|
+
response = execute_request(uri, request)
|
23
|
+
|
24
|
+
pr_data = parse_response(response)
|
25
|
+
filtered_prs = exclude_bots(pr_data)
|
26
|
+
correction_pr_count = count_correction_prs(filtered_prs)
|
27
|
+
|
28
|
+
output_metrics(period, filtered_prs, correction_pr_count)
|
29
|
+
puts "Done. Please check the file #{output_filename}"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def build_request(uri, period)
|
35
|
+
request = Net::HTTP::Post.new(uri)
|
36
|
+
request["Authorization"] = "Bearer #{@access_token}"
|
37
|
+
request.body = graphql_query(period)
|
38
|
+
|
39
|
+
request
|
40
|
+
end
|
41
|
+
|
42
|
+
def execute_request(uri, request)
|
43
|
+
options = { use_ssl: uri.scheme == 'https' }
|
44
|
+
Net::HTTP.start(uri.hostname, uri.port, options) { |http| http.request(request) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_response(response)
|
48
|
+
unless response.is_a?(Net::HTTPSuccess)
|
49
|
+
raise "Failed to fetch data: #{response.message} (#{response.code})"
|
50
|
+
end
|
51
|
+
|
52
|
+
JSON.parse(response.body).dig('data', 'search', 'edges')
|
53
|
+
end
|
54
|
+
|
55
|
+
def exclude_bots(prs)
|
56
|
+
return prs if @bot_accounts.empty?
|
57
|
+
prs.reject { |pr| @bot_accounts.include?(pr.dig('node', 'author', 'login')) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def count_correction_prs(prs)
|
61
|
+
prs.count { |pr| pr.dig('node', 'headRefName')&.match?(/^hotfix|fix|rollback/) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def calculate_lead_time(prs)
|
65
|
+
return "0d 00:00:00" if prs.empty?
|
66
|
+
|
67
|
+
times = prs.map do |pr|
|
68
|
+
merged_at = Time.parse(pr.dig('node', 'mergedAt'))
|
69
|
+
created_at = Time.parse(pr.dig('node', 'publishedAt'))
|
70
|
+
merged_at - created_at
|
71
|
+
end
|
72
|
+
|
73
|
+
average_time = times.sum.fdiv(times.size)
|
74
|
+
format_time(average_time)
|
75
|
+
end
|
76
|
+
|
77
|
+
def format_time(seconds)
|
78
|
+
return "0d 00:00:00" if seconds.nan? || seconds.infinite?
|
79
|
+
|
80
|
+
days, remaining = seconds.divmod(86_400)
|
81
|
+
Time.at(remaining).utc.strftime("#{days}d %H:%M:%S")
|
82
|
+
end
|
83
|
+
|
84
|
+
def graphql_query(period)
|
85
|
+
query_string = <<-GRAPHQL
|
86
|
+
{
|
87
|
+
search(query: "repo:#{@repo_name} is:pr merged:#{period}", type: ISSUE, first: 100) {
|
88
|
+
edges {
|
89
|
+
node {
|
90
|
+
... on PullRequest {
|
91
|
+
url
|
92
|
+
title
|
93
|
+
author {
|
94
|
+
login
|
95
|
+
}
|
96
|
+
mergedAt
|
97
|
+
headRefName
|
98
|
+
publishedAt
|
99
|
+
}
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
GRAPHQL
|
105
|
+
{ "query" => query_string.strip }.to_json
|
106
|
+
end
|
107
|
+
|
108
|
+
def output_metrics(period, prs, correction_pr_count)
|
109
|
+
formatted_data = format_data(period, prs, correction_pr_count)
|
110
|
+
|
111
|
+
File.open(output_filename, 'a') do |file|
|
112
|
+
file.write(formatted_data)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def format_data(period, prs, correction_pr_count)
|
117
|
+
raise NotImplementedError, "This method must be implemented by subclasses."
|
118
|
+
end
|
119
|
+
|
120
|
+
def output_filename
|
121
|
+
raise NotImplementedError, "This method must be implemented by subclasses."
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'client'
|
2
|
+
|
3
|
+
module DevMetrics
|
4
|
+
class Markdown < Client
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def format_data(period, prs, correction_pr_count)
|
9
|
+
output = ""
|
10
|
+
output << "| #{period} | #{prs.count} | #{correction_pr_count} | "
|
11
|
+
output << "#{((correction_pr_count.to_f / prs.count) * 100).round(2)}% | "
|
12
|
+
output << "#{calculate_lead_time(prs)} | "
|
13
|
+
output << "[PRs for #{period}](https://github.com/#{@repo_name}/pulls?q=is%3Apr+merged%3A#{period}) |\n"
|
14
|
+
output
|
15
|
+
end
|
16
|
+
|
17
|
+
def output_filename
|
18
|
+
"metrics_report.md"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/dev_metrics.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'dev_metrics/markdown'
|
2
|
+
|
3
|
+
module DevMetrics
|
4
|
+
class Config
|
5
|
+
attr_accessor :access_token, :repo_name, :bot_accounts
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@access_token = nil
|
9
|
+
@repo_name = nil
|
10
|
+
@bot_accounts = nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
@configuration = Config.new
|
15
|
+
|
16
|
+
def self.configuration
|
17
|
+
@configuration
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.configure
|
21
|
+
yield(configuration)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.run(format, date)
|
25
|
+
# 設定を使用してMarkdownクラスを呼び出す
|
26
|
+
markdown_processor = format.new(@configuration)
|
27
|
+
markdown_processor.process(period: date)
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dev_metrics
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- sean2121
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-09-04 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/dev_metrics.rb
|
20
|
+
- lib/dev_metrics/client.rb
|
21
|
+
- lib/dev_metrics/markdown.rb
|
22
|
+
homepage:
|
23
|
+
licenses:
|
24
|
+
- MIT
|
25
|
+
metadata: {}
|
26
|
+
post_install_message:
|
27
|
+
rdoc_options: []
|
28
|
+
require_paths:
|
29
|
+
- lib
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
requirements: []
|
41
|
+
rubygems_version: 3.2.33
|
42
|
+
signing_key:
|
43
|
+
specification_version: 4
|
44
|
+
summary: A RubyGem for tracking and analyzing development metrics from various repositories.
|
45
|
+
test_files: []
|