firespring_dev_commands 2.1.6.pre.alpha.2 → 2.1.7.pre.alpha.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 +4 -4
- data/lib/firespring_dev_commands/common.rb +1 -1
- data/lib/firespring_dev_commands/git.rb +7 -29
- data/lib/firespring_dev_commands/jira/histories.rb +11 -0
- data/lib/firespring_dev_commands/jira/history.rb +17 -0
- data/lib/firespring_dev_commands/jira/issue.rb +64 -3
- data/lib/firespring_dev_commands/jira/user.rb +3 -1
- data/lib/firespring_dev_commands/jira.rb +5 -3
- data/lib/firespring_dev_commands/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5efd3f846669710ea2f7f839df890d6489555a18bfe62ef77d644bf884c121e
|
4
|
+
data.tar.gz: 490a60e2c04726bbbb7d0950160395c3c3fb8c70313be14794fc6181296e7e0b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11e1f6e01faea39bc49278011efcce6c4f9b30a6dd283efea6e96f13938596e21ed840f961646e3b4d487db50eb4e3a0604a00e14e304eb351f09e2165e7b5b1
|
7
|
+
data.tar.gz: 48d04949c727bade11d7f405701d705fd25eb9a67cd25516a83c799159f32738bd4133028f3271fcf2cddb17a728c422bbed8781bc21f58bfc7d5689543ba555
|
@@ -41,7 +41,7 @@ module Dev
|
|
41
41
|
# If the user answers 'y' then the block is executed.
|
42
42
|
# If the user answers 'n' then the block is skipped.
|
43
43
|
def with_confirmation(message, default = 'y', color_message: true)
|
44
|
-
message = "\n #{message}
|
44
|
+
message = "\n #{message}? "
|
45
45
|
message = message.light_green if color_message
|
46
46
|
print message
|
47
47
|
print '('.light_green << 'y'.light_yellow << '/'.light_green << 'n'.light_yellow << ') '.light_green
|
@@ -203,7 +203,7 @@ module Dev
|
|
203
203
|
|
204
204
|
# Checks out the given branch in the given repo
|
205
205
|
# Defaults to the current directory
|
206
|
-
def checkout(branch, dir: default_project_dir
|
206
|
+
def checkout(branch, dir: default_project_dir)
|
207
207
|
raise 'branch is required' if branch.to_s.strip.empty?
|
208
208
|
return unless File.exist?(dir)
|
209
209
|
|
@@ -224,14 +224,12 @@ module Dev
|
|
224
224
|
indent g.pull('origin', actual_branch)
|
225
225
|
true
|
226
226
|
rescue ::Git::GitExecuteError => e
|
227
|
-
raise e if raise_errors
|
228
|
-
|
229
227
|
print_errors(e.message)
|
230
228
|
false
|
231
229
|
end
|
232
230
|
|
233
231
|
# Create the given branch in the given repo
|
234
|
-
def create_branch(branch, dir: default_project_dir
|
232
|
+
def create_branch(branch, dir: default_project_dir)
|
235
233
|
raise 'branch is required' if branch.to_s.strip.empty?
|
236
234
|
raise "refusing to create protected branch '#{branch}'" if %w(master develop).any?(branch.to_s.strip)
|
237
235
|
return unless File.exist?(dir)
|
@@ -242,7 +240,7 @@ module Dev
|
|
242
240
|
g = ::Git.open(dir)
|
243
241
|
g.fetch('origin', prune: true)
|
244
242
|
|
245
|
-
puts "Fetching the latest changes for base branch
|
243
|
+
puts "Fetching the latest changes for base branch #{staging_branch}"
|
246
244
|
g.checkout(staging_branch)
|
247
245
|
g.pull('origin', staging_branch)
|
248
246
|
|
@@ -253,19 +251,6 @@ module Dev
|
|
253
251
|
g.config("branch.#{branch}.merge", "refs/heads/#{branch}")
|
254
252
|
puts
|
255
253
|
rescue ::Git::GitExecuteError => e
|
256
|
-
raise e if raise_errors
|
257
|
-
|
258
|
-
print_errors(e.message)
|
259
|
-
false
|
260
|
-
end
|
261
|
-
|
262
|
-
def add(*paths, dir: default_project_dir, raise_errors: false)
|
263
|
-
g = ::Git.open(dir)
|
264
|
-
indent g.add(paths)
|
265
|
-
true
|
266
|
-
rescue ::Git::GitExecuteError => e
|
267
|
-
raise e if raise_errors
|
268
|
-
|
269
254
|
print_errors(e.message)
|
270
255
|
false
|
271
256
|
end
|
@@ -292,7 +277,7 @@ module Dev
|
|
292
277
|
end
|
293
278
|
|
294
279
|
# Merge the given branch into the given repo
|
295
|
-
def merge(branch, dir: default_project_dir
|
280
|
+
def merge(branch, dir: default_project_dir)
|
296
281
|
raise 'branch is required' if branch.to_s.strip.empty?
|
297
282
|
return unless File.exist?(dir)
|
298
283
|
|
@@ -311,8 +296,6 @@ module Dev
|
|
311
296
|
indent g.merge(branch)
|
312
297
|
true
|
313
298
|
rescue ::Git::GitExecuteError => e
|
314
|
-
raise e if raise_errors
|
315
|
-
|
316
299
|
print_errors(e.message)
|
317
300
|
false
|
318
301
|
end
|
@@ -337,7 +320,7 @@ module Dev
|
|
337
320
|
end
|
338
321
|
|
339
322
|
# Pull the given repo
|
340
|
-
def pull(dir: default_project_dir
|
323
|
+
def pull(dir: default_project_dir)
|
341
324
|
return unless File.exist?(dir)
|
342
325
|
|
343
326
|
g = ::Git.open(dir)
|
@@ -348,8 +331,6 @@ module Dev
|
|
348
331
|
indent g.pull('origin', branch)
|
349
332
|
true
|
350
333
|
rescue ::Git::GitExecuteError => e
|
351
|
-
raise e if raise_errors
|
352
|
-
|
353
334
|
print_errors(e.message)
|
354
335
|
false
|
355
336
|
end
|
@@ -374,7 +355,7 @@ module Dev
|
|
374
355
|
end
|
375
356
|
|
376
357
|
# Push the given repo
|
377
|
-
def push(dir: default_project_dir
|
358
|
+
def push(dir: default_project_dir)
|
378
359
|
return unless File.exist?(dir)
|
379
360
|
|
380
361
|
g = ::Git.open(dir)
|
@@ -385,8 +366,6 @@ module Dev
|
|
385
366
|
indent g.push('origin', branch)
|
386
367
|
true
|
387
368
|
rescue ::Git::GitExecuteError => e
|
388
|
-
raise e if raise_errors
|
389
|
-
|
390
369
|
print_errors(e.message)
|
391
370
|
false
|
392
371
|
end
|
@@ -399,7 +378,7 @@ module Dev
|
|
399
378
|
# Clones the repo_name into the dir
|
400
379
|
# Optionally specify a repo_org
|
401
380
|
# Optionally specify a branch to check out (defaults to the repository default branch)
|
402
|
-
def clone_repo(dir:, repo_name:, repo_org: 'firespring', branch: nil
|
381
|
+
def clone_repo(dir:, repo_name:, repo_org: 'firespring', branch: nil)
|
403
382
|
if Dir.exist?("#{dir}/.git")
|
404
383
|
puts "#{dir} already cloned".light_green
|
405
384
|
return
|
@@ -411,7 +390,6 @@ module Dev
|
|
411
390
|
|
412
391
|
opts = {}
|
413
392
|
opts[:branch] = branch unless branch.to_s.strip.empty?
|
414
|
-
opts[:depth] = depth unless depth.to_s.strip.empty?
|
415
393
|
g = ::Git.clone(ssh_repo_url(repo_name, repo_org), dir, opts)
|
416
394
|
g.fetch('origin', prune: true)
|
417
395
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Dev
|
4
|
+
class Jira
|
5
|
+
class History
|
6
|
+
attr_accessor :date, :id, :author, :created, :items
|
7
|
+
|
8
|
+
def initialize(data)
|
9
|
+
@data = data
|
10
|
+
@id = data['id']
|
11
|
+
@author = data['author']
|
12
|
+
@items = data['items']
|
13
|
+
@created = Time.parse(data['created'])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -3,9 +3,9 @@ module Dev
|
|
3
3
|
# Contains information and methods representing a Jira issue
|
4
4
|
class Issue
|
5
5
|
# Issue subtypes which do not map to a story type
|
6
|
-
NON_STORY_TYPES = ['review', 'sub-task', 'code review sub-task', 'pre-deploy sub-task', 'deploy sub-task', 'devops sub-task'].freeze
|
6
|
+
NON_STORY_TYPES = ['epic', 'review', 'sub-task', 'code review sub-task', 'pre-deploy sub-task', 'deploy sub-task', 'devops sub-task'].freeze
|
7
7
|
|
8
|
-
attr_accessor :data, :project, :id, :title, :points, :assignee, :resolved_date
|
8
|
+
attr_accessor :data, :project, :id, :title, :points, :assignee, :resolved_date, :histories, :last_in_progress_history, :first_in_review_history, :last_closed_history
|
9
9
|
|
10
10
|
def initialize(data)
|
11
11
|
@data = data
|
@@ -15,10 +15,71 @@ module Dev
|
|
15
15
|
@points = calculate_points(data)
|
16
16
|
@assignee = Jira::User.lookup(data.assignee&.accountId)
|
17
17
|
@resolved_date = data.resolutiondate
|
18
|
+
@histories = Jira::Histories.populate(data)
|
19
|
+
@last_in_progress_history = nil
|
20
|
+
@first_in_review_history = nil
|
21
|
+
@last_closed_history = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def cycle_time
|
25
|
+
# Calculate the difference and convert to days
|
26
|
+
((last_closed_history.created - last_in_progress_history.created) / 60 / 60 / 24).round(2)
|
27
|
+
end
|
28
|
+
|
29
|
+
def in_progress_cycle_time
|
30
|
+
# Calculate the difference and convert to days
|
31
|
+
((first_in_review_history.created - last_in_progress_history.created) / 60 / 60 / 24).round(2)
|
32
|
+
end
|
33
|
+
|
34
|
+
def in_review_cycle_time
|
35
|
+
# Calculate the difference and convert to days
|
36
|
+
((last_closed_history.created - first_in_review_history.created) / 60 / 60 / 24).round(2)
|
37
|
+
end
|
38
|
+
|
39
|
+
private def last_in_progress_history
|
40
|
+
raise 'you must expand the changelog field to calculate cycle time' if histories.nil?
|
41
|
+
|
42
|
+
# Find the first instance in the histoy where the status moved to "In Progress"
|
43
|
+
@last_in_progress_history ||= histories.select do |history|
|
44
|
+
history.items.find do |item|
|
45
|
+
item['fieldId'] == 'status' && item['fromString'] == 'Open' && item['toString'] == 'In Progress'
|
46
|
+
end
|
47
|
+
end.max_by(&:created)
|
48
|
+
raise 'unable to find "In Progress" history entry needed to calculate cycle time' unless @last_in_progress_history
|
49
|
+
|
50
|
+
@last_in_progress_history
|
51
|
+
end
|
52
|
+
|
53
|
+
private def first_in_review_history
|
54
|
+
raise 'you must expand the changelog field to calculate cycle time' if histories.nil?
|
55
|
+
|
56
|
+
# Find the first instance in the histoy where the status moved to "In Review"
|
57
|
+
@first_in_review_history ||= histories.select do |history|
|
58
|
+
history.items.find do |item|
|
59
|
+
item['fieldId'] == 'status' && item['toString'] == 'In Review'
|
60
|
+
end
|
61
|
+
end.min_by(&:created)
|
62
|
+
raise 'unable to find "In Review" history entry needed to calculate cycle time' unless @first_in_review_history
|
63
|
+
|
64
|
+
@first_in_review_history
|
65
|
+
end
|
66
|
+
|
67
|
+
private def last_closed_history
|
68
|
+
raise 'you must expand the changelog field to calculate cycle time' if histories.nil?
|
69
|
+
|
70
|
+
# Find the last instance in the histoy where the status moved to "Closed"
|
71
|
+
@last_closed_history ||= histories.select do |history|
|
72
|
+
history.items.find do |item|
|
73
|
+
item['fieldId'] == 'status' && item['toString'] == 'Closed'
|
74
|
+
end
|
75
|
+
end.max_by(&:created)
|
76
|
+
raise 'unable to find "Closed" history entry needed to calculate cycle time' unless @last_closed_history
|
77
|
+
|
78
|
+
@last_closed_history
|
18
79
|
end
|
19
80
|
|
20
81
|
# Returns the value of the jira points field or 0 if the field is not found
|
21
|
-
def calculate_points(data)
|
82
|
+
private def calculate_points(data)
|
22
83
|
return data.send(Dev::Jira.config.points_field_name).to_i if Dev::Jira.config.points_field_name && data.respond_to?(Dev::Jira.config.points_field_name)
|
23
84
|
|
24
85
|
0
|
@@ -22,8 +22,10 @@ module Dev
|
|
22
22
|
# Returns the Jira user object which maps to the give user id
|
23
23
|
# If none is found, it returns a Jira user object with only the id set
|
24
24
|
def self.lookup(id)
|
25
|
+
id = id.to_s.strip
|
26
|
+
id = 'notfound' if id.empty?
|
25
27
|
user = Dev::Jira.config.user_lookup_list&.find { |it| it.id == id }
|
26
|
-
user ||= new(name: '', email: '', id:)
|
28
|
+
user ||= new(name: 'Not Found', email: 'notfound@notfound.com', id:)
|
27
29
|
user
|
28
30
|
end
|
29
31
|
end
|
@@ -9,12 +9,13 @@ module Dev
|
|
9
9
|
# "user_lookup_list" should be an array of Jira::User objects representing the usernames, ids, etc for all jira users
|
10
10
|
# This is a bit clumsy but currently the jira api only returns the user id with issues
|
11
11
|
# and there is no way to query this information from Jira directly.
|
12
|
-
Config = Struct.new(:username, :token, :url, :points_field_name, :user_lookup_list, :read_timeout, :http_debug) do
|
12
|
+
Config = Struct.new(:username, :token, :url, :points_field_name, :expand, :user_lookup_list, :read_timeout, :http_debug) do
|
13
13
|
def initialize
|
14
14
|
self.username = nil
|
15
15
|
self.token = nil
|
16
16
|
self.url = nil
|
17
17
|
self.points_field_name = nil
|
18
|
+
self.expand = []
|
18
19
|
self.user_lookup_list = []
|
19
20
|
self.read_timeout = 120
|
20
21
|
self.http_debug = false
|
@@ -62,15 +63,16 @@ module Dev
|
|
62
63
|
def issues(jql, &)
|
63
64
|
start_at = 0
|
64
65
|
max_results = 100
|
66
|
+
expand = self.class.config.expand
|
65
67
|
|
66
68
|
# Query Jira and yield all issues it returns
|
67
|
-
issues = @client.Issue.jql(jql, start_at:, max_results:)
|
69
|
+
issues = @client.Issue.jql(jql, start_at:, max_results:, expand:)
|
68
70
|
issues.map { |data| Issue.new(data) }.each(&)
|
69
71
|
|
70
72
|
# If we returned the max_results then there may be more - add the max results to where we start at and query again
|
71
73
|
while issues.length >= max_results
|
72
74
|
start_at += max_results
|
73
|
-
issues = @client.Issue.jql(jql, start_at:, max_results:)
|
75
|
+
issues = @client.Issue.jql(jql, start_at:, max_results:, expand:)
|
74
76
|
issues.map { |data| Issue.new(data) }.each(&)
|
75
77
|
end
|
76
78
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: firespring_dev_commands
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.7.pre.alpha.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Firespring
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09-
|
11
|
+
date: 2023-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -284,6 +284,8 @@ files:
|
|
284
284
|
- lib/firespring_dev_commands/git.rb
|
285
285
|
- lib/firespring_dev_commands/git/info.rb
|
286
286
|
- lib/firespring_dev_commands/jira.rb
|
287
|
+
- lib/firespring_dev_commands/jira/histories.rb
|
288
|
+
- lib/firespring_dev_commands/jira/history.rb
|
287
289
|
- lib/firespring_dev_commands/jira/issue.rb
|
288
290
|
- lib/firespring_dev_commands/jira/project.rb
|
289
291
|
- lib/firespring_dev_commands/jira/user.rb
|