fire_watch 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf637f8c3306b80859e71da69845909e8857fa5e
4
- data.tar.gz: b66b910931b402ba8415da646c12fe3974c86418
3
+ metadata.gz: 9318eee1f045e2414c745fad818fd230c62e1076
4
+ data.tar.gz: 2a0630f217ece84f8abf28b9c694998769885d71
5
5
  SHA512:
6
- metadata.gz: cc64f6f3f9f5f8bf5e210254f507be032645eb3cfd9cd48b918afe2f53817861485ae39c4ab16addb3a3a1e30c58b9f64141b7e75d0ecaa7fdd24532f839f2fb
7
- data.tar.gz: 792fa2e234b0192fa7e8843caee140c4d210a582a6c4fa081fdbb9729dbb4b52cea5d6e25d85876bd5998c036ed7191936bf9844c05bfa5e13b778469e67b812
6
+ metadata.gz: 17ace434edd54fbd83e7d7ce72f627d638fbd1d55e132f80e1b5e532764c1f867ed8af595b632241068fb8a425a190823db33107786be9367b69d804071ea0e7
7
+ data.tar.gz: 1f3bdbaeb09eed00c3c7286068ecf3310fe497240265eba4f2dcb3d35c35935d7d5a411197d7d0267db4c39426f191f9ce18d0dea09063aad462a8847ef836dd
@@ -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", "~> 3.1"
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"
@@ -1,4 +1,5 @@
1
1
  require "fire_watch/version"
2
+ require "fire_watch/report_builder"
2
3
 
3
4
  module FireWatch
4
5
  # Your code goes here...
@@ -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
@@ -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
- @github_client = Github.new(auto_pagination: true) do |config|
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
- puts "Fetching Repos from Github..."
23
- repos = @github_client.repos.list(org: 'wildland').sort_by{|r| r.name}
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
- repos.each do |r|
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
- puts "Fetching Milestones from Github..."
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
- clients.sort_by(&:name).each do |client|
75
- projects.select{|p| p.client_id == client.id}.sort_by(&:name).each do |p|
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
- progressbar = ProgressBar.create(format: "%a %b\u{15E7}%i %p%% %t", progress_mark: ' ', remainder_mark: "\u{FF65}", starting_at: 10)
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
- selected_milestones.each do |selected_milestone|
134
- issues, pull_request_issues = @github_client.issues.list(user: 'wildland', repo: selected_repo.name, milestone: selected_milestone.number, state: 'all').partition{|i| i.pull_request == nil}
135
- pull_requests = pull_request_issues.map do |i|
136
- @github_client.pull_requests.get(user: 'wildland', repo: selected_repo.name, number: i.number).body
137
- end
138
-
139
- issues.each do |issue|
140
- relevant_time_entries, time_entries = time_entries.partition do |entry|
141
- entry.notes.nil? ? false : entry.notes.scan(ISSUE_REGEX).flatten.any?{|m| m.eql? issue.number.to_s }
142
- end
143
-
144
- if relevant_time_entries.empty?
145
- csv << [
146
- selected_repo.name,
147
- selected_milestone.title,
148
- issue.number,
149
- issue.title,
150
- "",
151
- "",
152
- !issue.labels.nil? && !issue.labels.find{|l| l.name.include?"size:"}.nil? ? issue.labels.find{|l| l.name.include?"size:"}.name : "",
153
- "",
154
- issue.state,
155
- "",
156
- issue.labels.nil? ? "" : issue.labels.compact.map{|l| l.name}.join(', ')
157
- ]
158
- else
159
- relevant_time_entries.chunk{|i| i.user_id }.each do |user_id, user_time_entries|
160
- user = harvest_users.find{|u| u.id == user_id}
161
- csv << [
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 time_entries.count > 0 && cli.agree("#{time_entries.count} Time entries not in this milestone. Create a list of them?")
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
- time_entries.each do |entry|
160
+ missing_harvest_time_entries.each do |l|
238
161
  csv << [
239
- entry.created_at,
240
- "#{harvest_users.find{|u| u.id == entry.user_id}.first_name} #{harvest_users.find{|u| u.id == entry.user_id}.last_name}",
241
- entry.hours,
242
- entry.notes
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
@@ -1,3 +1,3 @@
1
1
  module FireWatch
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
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.5.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-07-31 00:00:00.000000000 Z
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: '3.1'
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: '3.1'
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