oss-stats 0.0.1
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/CHANGELOG.md +5 -0
- data/CONTRIBUTING.md +32 -0
- data/Gemfile +11 -0
- data/LICENSE +201 -0
- data/README.md +110 -0
- data/bin/meeting_stats +450 -0
- data/bin/pipeline_visibility_stats +636 -0
- data/bin/promise_stats +312 -0
- data/bin/repo_stats +113 -0
- data/docs/MeetingStats.md +69 -0
- data/docs/PipelineVisibilityStats.md +51 -0
- data/docs/PromiseStats.md +56 -0
- data/docs/RepoStats.md +130 -0
- data/examples/meeting_stats_config.rb +22 -0
- data/examples/promise_stats_config.rb +23 -0
- data/examples/repo_stats_config.rb +49 -0
- data/initialization_data/Gemfile +3 -0
- data/initialization_data/README.md +20 -0
- data/initialization_data/rubocop.yml +2 -0
- data/lib/oss_stats/buildkite_client.rb +252 -0
- data/lib/oss_stats/buildkite_token.rb +15 -0
- data/lib/oss_stats/config/meeting_stats.rb +36 -0
- data/lib/oss_stats/config/promise_stats.rb +22 -0
- data/lib/oss_stats/config/repo_stats.rb +47 -0
- data/lib/oss_stats/config/shared.rb +43 -0
- data/lib/oss_stats/github_client.rb +55 -0
- data/lib/oss_stats/github_token.rb +23 -0
- data/lib/oss_stats/log.rb +25 -0
- data/lib/oss_stats/repo_stats.rb +1048 -0
- data/lib/oss_stats/version.rb +3 -0
- data/oss-stats.gemspec +39 -0
- data/spec/buildkite_client_spec.rb +171 -0
- data/spec/repo_stats_spec.rb +1242 -0
- metadata +181 -0
data/oss-stats.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative 'lib/oss_stats/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'oss-stats'
|
5
|
+
spec.version = OssStats::VERSION
|
6
|
+
spec.summary = 'Suite of tools for reporting health of F/OSS communities'
|
7
|
+
spec.authors = ['Phil Dibowitz']
|
8
|
+
spec.email = ['phil@ipom.com']
|
9
|
+
spec.license = 'Apache-2.0'
|
10
|
+
spec.homepage = 'https://github.com/jaymzh/oss-stats'
|
11
|
+
spec.required_ruby_version = '>= 3.2'
|
12
|
+
docs = %w{
|
13
|
+
README.md
|
14
|
+
LICENSE
|
15
|
+
Gemfile
|
16
|
+
oss-stats.gemspec
|
17
|
+
CONTRIBUTING.md
|
18
|
+
CHANGELOG.md
|
19
|
+
} + Dir.glob('examples/*') | Dir.glob('docs/*')
|
20
|
+
spec.extra_rdoc_files = docs
|
21
|
+
spec.executables += Dir.glob('bin/*').map { |x| File.basename(x) }
|
22
|
+
spec.files =
|
23
|
+
Dir.glob('lib/oss_stats/*.rb') +
|
24
|
+
Dir.glob('lib/oss_stats/config/*.rb') +
|
25
|
+
Dir.glob('bin/*') +
|
26
|
+
Dir.glob('extras/*') +
|
27
|
+
Dir.glob('spec/*') +
|
28
|
+
Dir.glob('initialization_data/*') +
|
29
|
+
Dir.glob('initialization_data/github_workflow/s*')
|
30
|
+
%w{sqlite3 octokit mixlib-log mixlib-config gruff deep_merge}.each do |dep|
|
31
|
+
spec.add_dependency dep
|
32
|
+
end
|
33
|
+
spec.metadata = {
|
34
|
+
'rubygems_mfa_required' => 'true',
|
35
|
+
'bug_tracker_uri' => 'https://github.com/jaymzh/oss-stats/issues',
|
36
|
+
'homepage_uri' => 'https://github.com/jaymzh/oss-stats',
|
37
|
+
'source_code_uri' => 'https://github.com/jaymzh/oss-stats',
|
38
|
+
}
|
39
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require_relative '../lib/oss_stats/buildkite_client'
|
2
|
+
require_relative '../lib/oss_stats/log'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
RSpec.describe OssStats::BuildkiteClient do
|
6
|
+
let(:token) { 'test-token' }
|
7
|
+
let(:organization_slug) { 'test-org' }
|
8
|
+
let(:client) { described_class.new(token) }
|
9
|
+
|
10
|
+
describe '#get_pipeline_builds' do
|
11
|
+
let(:pipeline_slug) { 'test-pipeline' }
|
12
|
+
let(:pull_request_id) { '123' }
|
13
|
+
let(:from_date) { Date.new(2024, 1, 1) }
|
14
|
+
let(:to_date) { Date.new(2024, 1, 31) }
|
15
|
+
|
16
|
+
context 'when the API returns builds' do
|
17
|
+
let(:mock_api_response) do
|
18
|
+
{
|
19
|
+
'data' => {
|
20
|
+
'pipeline' => {
|
21
|
+
'builds' => {
|
22
|
+
'edges' => [
|
23
|
+
{
|
24
|
+
'node' => {
|
25
|
+
'state' => 'PASSED',
|
26
|
+
},
|
27
|
+
},
|
28
|
+
{
|
29
|
+
'node' => {
|
30
|
+
'state' => 'FAILED',
|
31
|
+
},
|
32
|
+
},
|
33
|
+
],
|
34
|
+
'pageInfo' => {
|
35
|
+
'hasNextPage' => false,
|
36
|
+
'endCursor' => nil,
|
37
|
+
},
|
38
|
+
},
|
39
|
+
},
|
40
|
+
},
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
before do
|
45
|
+
# Mock the execute_graphql_query method
|
46
|
+
allow(client).to receive(:execute_graphql_query)
|
47
|
+
.and_return(mock_api_response)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'returns a list of builds with their job statuses' do
|
51
|
+
builds = client.get_pipeline_builds(
|
52
|
+
organization_slug, pipeline_slug, from_date, to_date
|
53
|
+
)
|
54
|
+
|
55
|
+
expect(builds.size).to eq(2)
|
56
|
+
expect(builds[0]['node']['state']).to eq('PASSED')
|
57
|
+
expect(builds[1]['node']['state']).to eq('FAILED')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when the API response is paginated' do
|
62
|
+
let(:mock_api_response_page1) do
|
63
|
+
{
|
64
|
+
'data' => {
|
65
|
+
'pipeline' => {
|
66
|
+
'builds' => {
|
67
|
+
'edges' => [
|
68
|
+
{ 'node' => { 'state' => 'PASSED' } },
|
69
|
+
],
|
70
|
+
'pageInfo' => {
|
71
|
+
'hasNextPage' => true, 'endCursor' => 'cursor1'
|
72
|
+
},
|
73
|
+
},
|
74
|
+
},
|
75
|
+
},
|
76
|
+
}
|
77
|
+
end
|
78
|
+
let(:mock_api_response_page2) do
|
79
|
+
{
|
80
|
+
'data' => {
|
81
|
+
'pipeline' => {
|
82
|
+
'builds' => {
|
83
|
+
'edges' => [
|
84
|
+
{ 'node' => { 'state' => 'FAILED' } },
|
85
|
+
],
|
86
|
+
'pageInfo' => { 'hasNextPage' => false, 'endCursor' => nil },
|
87
|
+
},
|
88
|
+
},
|
89
|
+
},
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
before do
|
94
|
+
# Mock execute_graphql_query to return page 1 then page 2
|
95
|
+
allow(client).to receive(:execute_graphql_query)
|
96
|
+
.and_return(mock_api_response_page1, mock_api_response_page2)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'fetches all builds across pages' do
|
100
|
+
builds = client.get_pipeline_builds(
|
101
|
+
organization_slug, pipeline_slug, from_date, to_date
|
102
|
+
)
|
103
|
+
expect(builds.size).to eq(2)
|
104
|
+
expect(builds[0]['node']['state']).to eq('PASSED')
|
105
|
+
expect(builds[1]['node']['state']).to eq('FAILED')
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'makes two API calls' do
|
109
|
+
expect(client).to receive(:execute_graphql_query).twice
|
110
|
+
client.get_pipeline_builds(
|
111
|
+
organization_slug, pipeline_slug, from_date, to_date
|
112
|
+
)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'when the API call fails' do
|
117
|
+
before do
|
118
|
+
allow(client).to receive(:execute_graphql_query)
|
119
|
+
.and_raise(StandardError.new('API Error'))
|
120
|
+
allow(OssStats::Log).to receive(:error)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'returns an empty array' do
|
124
|
+
builds = client.get_pipeline_builds(
|
125
|
+
organization_slug, pipeline_slug, from_date, to_date
|
126
|
+
)
|
127
|
+
expect(builds).to be_empty
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'logs the error' do
|
131
|
+
expected_log_message =
|
132
|
+
%r{Error in get_pipeline_builds for test-org/test-pipeline}
|
133
|
+
expect(OssStats::Log).to receive_message_chain(:error)
|
134
|
+
.with(expected_log_message)
|
135
|
+
client.get_pipeline_builds(
|
136
|
+
organization_slug, pipeline_slug, from_date, to_date
|
137
|
+
)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'when the API returns no builds' do
|
142
|
+
let(:mock_api_response) do
|
143
|
+
{
|
144
|
+
'data' => {
|
145
|
+
'pipeline' => {
|
146
|
+
'builds' => {
|
147
|
+
'edges' => [],
|
148
|
+
'pageInfo' => {
|
149
|
+
'hasNextPage' => false,
|
150
|
+
'endCursor' => nil,
|
151
|
+
},
|
152
|
+
},
|
153
|
+
},
|
154
|
+
},
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
before do
|
159
|
+
allow(client).to receive(:execute_graphql_query)
|
160
|
+
.and_return(mock_api_response)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'returns an empty array' do
|
164
|
+
builds = client.get_pipeline_builds(
|
165
|
+
organization_slug, pipeline_slug, from_date, to_date
|
166
|
+
)
|
167
|
+
expect(builds).to be_empty
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|