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 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
@@ -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: []