fire_watch 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/fire_watch.gemspec +2 -1
- data/lib/fire_watch.rb +1 -0
- data/lib/fire_watch/report_builder.rb +93 -0
- data/lib/fire_watch/runner.rb +54 -131
- data/lib/fire_watch/version.rb +1 -1
- metadata +19 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9318eee1f045e2414c745fad818fd230c62e1076
|
4
|
+
data.tar.gz: 2a0630f217ece84f8abf28b9c694998769885d71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17ace434edd54fbd83e7d7ce72f627d638fbd1d55e132f80e1b5e532764c1f867ed8af595b632241068fb8a425a190823db33107786be9367b69d804071ea0e7
|
7
|
+
data.tar.gz: 1f3bdbaeb09eed00c3c7286068ecf3310fe497240265eba4f2dcb3d35c35935d7d5a411197d7d0267db4c39426f191f9ce18d0dea09063aad462a8847ef836dd
|
data/fire_watch.gemspec
CHANGED
@@ -29,10 +29,11 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
30
|
spec.require_paths = ["lib"]
|
31
31
|
|
32
|
-
spec.add_runtime_dependency "harvested", "~>
|
32
|
+
spec.add_runtime_dependency "harvested", "~> 4.0"
|
33
33
|
spec.add_runtime_dependency "highline", "~> 1.7"
|
34
34
|
spec.add_runtime_dependency "github_api", "~> 0.17"
|
35
35
|
spec.add_runtime_dependency "ruby-progressbar", "~>1.8"
|
36
|
+
spec.add_runtime_dependency 'parallel', '1.12.0'
|
36
37
|
|
37
38
|
spec.add_development_dependency "bundler", "~> 1.15"
|
38
39
|
spec.add_development_dependency "rake", "~> 10.0"
|
data/lib/fire_watch.rb
CHANGED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
module FireWatch
|
3
|
+
class ReportBuilder
|
4
|
+
ISSUE_REGEX = /.?#(\d+)/
|
5
|
+
attr_reader :github_client, :harvest_client, :github_repos, :harvest_clients, :harvest_projects, :harvest_users, :time_entries
|
6
|
+
|
7
|
+
def initialize(github_client:, harvest_client:)
|
8
|
+
@github_client = github_client
|
9
|
+
@harvest_client = harvest_client
|
10
|
+
@github_repos, @harvest_clients, @harvest_projects, @harvest_users = Parallel.map([
|
11
|
+
@github_client.repos.list(org: 'wildland').sort_by{|r| r.name},
|
12
|
+
@harvest_client.clients.all,
|
13
|
+
@harvest_client.projects.all,
|
14
|
+
@harvest_client.users.all
|
15
|
+
]) {|j| j}
|
16
|
+
end
|
17
|
+
|
18
|
+
def issue_pull_request_report(selected_github_repo:, selected_github_milestones:, selected_harvest_projects:)
|
19
|
+
@time_entries = Parallel.map(
|
20
|
+
selected_harvest_projects.select{|p| !p.starts_on.nil?}.map do |project|
|
21
|
+
@harvest_client.reports.time_by_project(
|
22
|
+
project,
|
23
|
+
Time.parse(project.starts_on),
|
24
|
+
project.ends_on.nil? ? Time.now : Time.parse(project.ends_on)
|
25
|
+
)
|
26
|
+
end
|
27
|
+
){|j| j}
|
28
|
+
@time_entries.flatten!
|
29
|
+
github_report = []
|
30
|
+
|
31
|
+
selected_github_milestones.each do |selected_milestone|
|
32
|
+
issues, pull_request_issues = @github_client.issues.list(user: 'wildland', repo: selected_github_repo.name, milestone: selected_milestone.number, state: 'all').partition{|i| i.pull_request == nil}
|
33
|
+
pull_requests = pull_request_issues.map do |i|
|
34
|
+
@github_client.pull_requests.get(user: 'wildland', repo: selected_github_repo.name, number: i.number).body
|
35
|
+
end
|
36
|
+
|
37
|
+
issues.each do |issue|
|
38
|
+
github_report << report_line(
|
39
|
+
issue: issue,
|
40
|
+
pull_request: nil,
|
41
|
+
selected_github_repo: selected_github_repo,
|
42
|
+
selected_github_milestone: selected_milestone
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
pull_requests.each do |pr|
|
47
|
+
github_report << report_line(
|
48
|
+
issue: pr,
|
49
|
+
pull_request: pr,
|
50
|
+
selected_github_repo: selected_github_repo,
|
51
|
+
selected_github_milestone: selected_milestone
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
return github_report, @time_entries.map do |e|
|
57
|
+
{
|
58
|
+
time_entry: e,
|
59
|
+
harvest_user: @harvest_users.find{|u| u.id == e.user_id}
|
60
|
+
}
|
61
|
+
end # TODO format time_entries that are missing
|
62
|
+
end
|
63
|
+
|
64
|
+
def report_line(issue:, pull_request:, selected_github_repo:, selected_github_milestone:)
|
65
|
+
github_report_line = {
|
66
|
+
github_repo: selected_github_repo,
|
67
|
+
github_milestone: selected_github_milestone,
|
68
|
+
github_estimated_size_label: !issue.labels.nil? && !issue.labels.find{|l| l.name.include?"size:"}.nil? ? issue.labels.find{|l| l.name.include?"size:"} : nil,
|
69
|
+
issue: issue,
|
70
|
+
pull_request: pull_request,
|
71
|
+
harvest_time_entries: []
|
72
|
+
}
|
73
|
+
relevant_time_entries, @time_entries = @time_entries.partition do |entry|
|
74
|
+
entry.notes.nil? ? false : entry.notes.scan(ISSUE_REGEX).flatten.any?{|m| m.eql? github_report_line[:issue].number.to_s }
|
75
|
+
end
|
76
|
+
if !relevant_time_entries.empty?
|
77
|
+
relevant_time_entries.chunk{|i| i.user_id }.each do |user_id, user_time_entries|
|
78
|
+
user = harvest_users.find{|u| u.id == user_id}
|
79
|
+
github_report_line[:harvest_time_entries] << {
|
80
|
+
harvest_user: user,
|
81
|
+
time_entries: user_time_entries,
|
82
|
+
harvest_projects: @harvest_projects.select{|p| user_time_entries.map(&:project_id).uniq.include? p.id}
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
@time_entries.concat relevant_time_entries.select { |entry|
|
87
|
+
entry.notes.nil? ? false : entry.notes.scan(ISSUE_REGEX).flatten.count > 1
|
88
|
+
}
|
89
|
+
end
|
90
|
+
return github_report_line
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/fire_watch/runner.rb
CHANGED
@@ -5,36 +5,41 @@ require 'ruby-progressbar'
|
|
5
5
|
|
6
6
|
module FireWatch
|
7
7
|
class Runner
|
8
|
-
ISSUE_REGEX = /.?#(\d+)/
|
9
8
|
def self.invoke
|
10
9
|
|
11
10
|
cli = HighLine.new
|
12
11
|
# Github Client
|
13
12
|
github_login = cli.ask('Github Login: ')
|
14
13
|
github_password = cli.ask('Github Password: ') { |q| q.echo = 'x' }
|
15
|
-
|
14
|
+
github_client = Github.new(auto_pagination: true) do |config|
|
16
15
|
config.basic_auth = "#{github_login}:#{github_password}"
|
17
16
|
if cli.agree("Do you use Two-Factor authentication (non-sms)?")
|
18
17
|
config.connection_options = { headers: {"X-GitHub-OTP" => cli.ask('Two-Factor Code')} }
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
22
|
-
|
23
|
-
|
21
|
+
# Harvest Client
|
22
|
+
harvest_username = cli.ask('Harvest Username: ')
|
23
|
+
harvest_password = cli.ask('Harvest Password: ') { |q| q.echo = 'x' }
|
24
|
+
harvest_client = Harvest.client(username: harvest_username, password: harvest_password, subdomain: 'wildland')
|
25
|
+
|
26
|
+
@report_builder = ReportBuilder.new(
|
27
|
+
github_client: github_client,
|
28
|
+
harvest_client: harvest_client
|
29
|
+
)
|
24
30
|
|
25
31
|
|
26
32
|
selected_repo = nil
|
27
33
|
cli.choose do |menu|
|
28
34
|
menu.prompt = "Select Github Repo:"
|
29
|
-
|
35
|
+
@report_builder.github_repos.each do |r|
|
30
36
|
menu.choice(r.name) do
|
31
37
|
selected_repo = r
|
32
38
|
end
|
33
39
|
end
|
34
40
|
end
|
35
41
|
|
36
|
-
|
37
|
-
milestones = @github_client.issues.milestones.list(user: 'wildland',repo: selected_repo.name, state: 'all').sort_by{|m| m.title}
|
42
|
+
milestones = @report_builder.github_client.issues.milestones.list(user: 'wildland',repo: selected_repo.name, state: 'all').sort_by{|m| m.title}
|
38
43
|
|
39
44
|
selected_milestones = []
|
40
45
|
done = false
|
@@ -55,15 +60,6 @@ module FireWatch
|
|
55
60
|
break if done
|
56
61
|
end
|
57
62
|
|
58
|
-
# Harvest Client
|
59
|
-
harvest_username = cli.ask('Harvest Username: ')
|
60
|
-
harvest_password = cli.ask('Harvest Password: ') { |q| q.echo = 'x' }
|
61
|
-
@harvest_client = Harvest.client(username: harvest_username, password: harvest_password, subdomain: 'wildland')
|
62
|
-
|
63
|
-
puts "Fetching Clients and Projects from Harvest..."
|
64
|
-
clients = @harvest_client.clients.all
|
65
|
-
projects = @harvest_client.projects.all
|
66
|
-
|
67
63
|
selected_projects = []
|
68
64
|
done = false
|
69
65
|
show_all = false
|
@@ -71,8 +67,8 @@ module FireWatch
|
|
71
67
|
cli.choose do |menu|
|
72
68
|
menu.prompt = "Add/Remove Project (#{selected_projects.map(&:name).join(', ')}): "
|
73
69
|
menu.choice("Done Adding Projects") { done = true } if selected_projects.count > 0
|
74
|
-
|
75
|
-
|
70
|
+
@report_builder.harvest_clients.sort_by(&:name).each do |client|
|
71
|
+
@report_builder.harvest_projects.select{|p| p.client_id == client.id}.sort_by(&:name).each do |p|
|
76
72
|
next unless show_all || p.active
|
77
73
|
menu.choice("#{client.name} - #{p.name}") do
|
78
74
|
if selected_projects.include? p
|
@@ -92,29 +88,16 @@ module FireWatch
|
|
92
88
|
break if done
|
93
89
|
end
|
94
90
|
|
95
|
-
puts "Fetching Users from Harvest..."
|
96
|
-
harvest_users = @harvest_client.users.all
|
97
|
-
|
98
|
-
time_entries = []
|
99
|
-
|
100
|
-
selected_projects.each do |project|
|
101
|
-
if project.starts_on.nil?
|
102
|
-
puts "No start date for #{project.name}"
|
103
|
-
next
|
104
|
-
end
|
105
|
-
time_entries += @harvest_client.reports.time_by_project(
|
106
|
-
project,
|
107
|
-
Time.parse(project.starts_on),
|
108
|
-
project.ends_on.nil? ? Time.now : Time.parse(project.ends_on)
|
109
|
-
)
|
110
|
-
end
|
111
|
-
|
112
91
|
#Process Data
|
113
92
|
csv_file_name = cli.ask('File name for report?').concat('.csv')
|
114
93
|
|
115
94
|
puts "Generating Report with " + selected_milestones.map(&:title).join(', ') + " vs " + selected_projects.map(&:name).join(', ')
|
116
95
|
puts "Saving to #{csv_file_name}"
|
117
|
-
|
96
|
+
github_report, missing_harvest_time_entries = @report_builder.issue_pull_request_report(
|
97
|
+
selected_github_repo: selected_repo,
|
98
|
+
selected_github_milestones: selected_milestones,
|
99
|
+
selected_harvest_projects: selected_projects
|
100
|
+
)
|
118
101
|
CSV.open(csv_file_name, "wb", force_quotes: true) do |csv|
|
119
102
|
csv << [
|
120
103
|
"Repo",
|
@@ -130,102 +113,42 @@ module FireWatch
|
|
130
113
|
"Labels"
|
131
114
|
]
|
132
115
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
]
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
selected_repo.name,
|
163
|
-
selected_milestone.title,
|
164
|
-
issue.number,
|
165
|
-
issue.title,
|
166
|
-
projects.select{|p| user_time_entries.map(&:project_id).uniq.include? p.id}.map(&:name).join(', '),
|
167
|
-
"#{user.first_name} #{user.last_name}", # Who
|
168
|
-
!issue.labels.nil? && !issue.labels.find{|l| l.name.include?"size:"}.nil? ? issue.labels.find{|l| l.name.include?"size:"}.name : "",
|
169
|
-
user_time_entries.inject(0){|sum, e| sum + (e.hours / e.notes.scan(ISSUE_REGEX).flatten.count) },
|
170
|
-
issue.state,
|
171
|
-
"",
|
172
|
-
issue.labels.nil? ? "" : issue.labels.map{|l| l.name}.join(', ')
|
173
|
-
]
|
174
|
-
end
|
175
|
-
time_entries.concat relevant_time_entries.select { |entry|
|
176
|
-
entry.notes.nil? ? false : entry.notes.scan(ISSUE_REGEX).flatten.count > 1
|
177
|
-
}
|
178
|
-
end
|
179
|
-
progressbar.increment
|
180
|
-
end
|
181
|
-
|
182
|
-
pull_requests.each do |pr|
|
183
|
-
relevant_time_entries, time_entries = time_entries.partition do |entry|
|
184
|
-
entry.notes.nil? ? false : entry.notes.scan(ISSUE_REGEX).flatten.any?{|m| m.eql? pr.number.to_s }
|
185
|
-
end
|
186
|
-
|
187
|
-
if relevant_time_entries.empty?
|
188
|
-
csv << [
|
189
|
-
selected_repo.name,
|
190
|
-
selected_milestone.title,
|
191
|
-
pr.number,
|
192
|
-
pr.title,
|
193
|
-
"",
|
194
|
-
"",
|
195
|
-
!pr.labels.nil? && !pr.labels.find{|l| l.name.include?"size:"}.nil? ? pr.labels.find{|l| l.name.include?"size:"}.name : "",
|
196
|
-
"",
|
197
|
-
pr.state,
|
198
|
-
pr.merged ? "Y" : "N",
|
199
|
-
pr.labels.nil? ? "" : pr.labels.map{|l| l.name}.join(', ')
|
116
|
+
github_report.each do |row|
|
117
|
+
if !row[:harvest_time_entries].any?
|
118
|
+
csv << [
|
119
|
+
row[:github_repo].name,
|
120
|
+
row[:github_milestone].title,
|
121
|
+
row[:issue].number,
|
122
|
+
row[:issue].title,
|
123
|
+
'',
|
124
|
+
'',
|
125
|
+
(row[:github_estimated_size_label].nil? ? '' : row[:github_estimated_size_label].name),
|
126
|
+
'',
|
127
|
+
row[:issue].state,
|
128
|
+
(row[:pull_request].nil? ? '' : (row[:pull_request].merged ? 'Y' : 'N')),
|
129
|
+
(row[:issue].labels.nil? ? '' : row[:issue].labels.map{|label| label.name}.join(' , ')),
|
130
|
+
]
|
131
|
+
else
|
132
|
+
row[:harvest_time_entries].each do |harvest_entries|
|
133
|
+
csv << [
|
134
|
+
row[:github_repo].name,
|
135
|
+
row[:github_milestone].title,
|
136
|
+
row[:issue].number,
|
137
|
+
row[:issue].title,
|
138
|
+
harvest_entries[:harvest_projects].map(&:name).join(', '),
|
139
|
+
"#{harvest_entries[:harvest_user].first_name} #{harvest_entries[:harvest_user].last_name}",
|
140
|
+
(row[:github_estimated_size_label].nil? ? '' : row[:github_estimated_size_label].name),
|
141
|
+
(harvest_entries[:time_entries].inject(0){|sum, e| sum + (e.hours / e.notes.scan(ReportBuilder::ISSUE_REGEX).flatten.count) }),
|
142
|
+
row[:issue].state,
|
143
|
+
(row[:pull_request].nil? ? '' : (row[:pull_request].merged ? 'Y' : 'N')),
|
144
|
+
(row[:issue].labels.nil? ? '' : row[:issue].labels.map{|label| label.name}.join(' , '))
|
200
145
|
]
|
201
|
-
else
|
202
|
-
relevant_time_entries.chunk{|i| i.user_id }.each do |user_id, user_time_entries|
|
203
|
-
user = harvest_users.find{|u| u.id == user_id}
|
204
|
-
csv << [
|
205
|
-
selected_repo.name,
|
206
|
-
selected_milestone.title,
|
207
|
-
pr.number,
|
208
|
-
pr.title,
|
209
|
-
projects.select{|p| user_time_entries.map(&:project_id).uniq.include? p.id}.map(&:name).join(', '),
|
210
|
-
"#{user.first_name} #{user.last_name}", # Who
|
211
|
-
!pr.labels.nil? && !pr.labels.find{|l| l.name.include?"size:"}.nil? ? pr.labels.find{|l| l.name.include?"size:"}.name : "",
|
212
|
-
user_time_entries.inject(0){|sum, e| sum + (e.hours / e.notes.scan(ISSUE_REGEX).flatten.count) },
|
213
|
-
pr.state,
|
214
|
-
pr.merged ? "Y" : "N",
|
215
|
-
pr.labels.nil? ? "" : issue.labels.map{|l| l.name}.join(', ')
|
216
|
-
]
|
217
|
-
end
|
218
|
-
time_entries.concat relevant_time_entries.select { |entry|
|
219
|
-
entry.notes.nil? ? false : entry.notes.scan(ISSUE_REGEX).flatten.count > 1
|
220
|
-
}
|
221
146
|
end
|
222
|
-
progressbar.increment
|
223
147
|
end
|
224
148
|
end
|
225
149
|
end
|
226
|
-
progressbar.finish
|
227
150
|
|
228
|
-
if
|
151
|
+
if missing_harvest_time_entries.count > 0 && cli.agree("#{missing_harvest_time_entries.count} Time entries not in this milestone. Create a list of them?")
|
229
152
|
time_entries_file_name = cli.ask('File name for report?').concat('.csv')
|
230
153
|
CSV.open(time_entries_file_name, "wb", force_quotes: true) do |csv|
|
231
154
|
csv << [
|
@@ -234,12 +157,12 @@ module FireWatch
|
|
234
157
|
"Hours",
|
235
158
|
"Notes"
|
236
159
|
]
|
237
|
-
|
160
|
+
missing_harvest_time_entries.each do |l|
|
238
161
|
csv << [
|
239
|
-
|
240
|
-
"#{
|
241
|
-
|
242
|
-
|
162
|
+
l[:time_entry].created_at,
|
163
|
+
"#{l[:harvest_user].first_name} #{l[:harvest_user].last_name}",
|
164
|
+
l[:time_entry].hours,
|
165
|
+
l[:time_entry].notes
|
243
166
|
]
|
244
167
|
end
|
245
168
|
end
|
data/lib/fire_watch/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fire_watch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Weakley
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: harvested
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '4.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '4.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: highline
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: parallel
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.12.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.12.0
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: bundler
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -127,6 +141,7 @@ files:
|
|
127
141
|
- exe/fire_watch
|
128
142
|
- fire_watch.gemspec
|
129
143
|
- lib/fire_watch.rb
|
144
|
+
- lib/fire_watch/report_builder.rb
|
130
145
|
- lib/fire_watch/runner.rb
|
131
146
|
- lib/fire_watch/version.rb
|
132
147
|
homepage: https://github.com/wildland/fire_watch
|