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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17a02c0b5521ca1da3efb1b8374529732d7dde86060a9864f1d75be1ba070f6f
4
- data.tar.gz: 9264d32211c1d1da7726215e8bf2c4f5df187f915ed9c38c29cdf336675b7e8b
3
+ metadata.gz: b5efd3f846669710ea2f7f839df890d6489555a18bfe62ef77d644bf884c121e
4
+ data.tar.gz: 490a60e2c04726bbbb7d0950160395c3c3fb8c70313be14794fc6181296e7e0b
5
5
  SHA512:
6
- metadata.gz: 436338662d1889eec74a3c5fac8ca50427fa0f58ec2a6a256f7689015217bc8738618e26b14260d3648e650d93bfa4ce64d9fc49ec974204815fb49d2ec09a6b
7
- data.tar.gz: 30158eb11227b227d711011bd77db61b1dc8bac6a8fda0760a89e21b8d60434a3f443162499313312d285ccf8c3cac751a5ad106f80c1bfa0e9139d973662028
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}" << '? '.light_green
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, raise_errors: false)
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, raise_errors: false)
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 \"#{staging_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, raise_errors: false)
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, raise_errors: false)
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, raise_errors: false)
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, depth: 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,11 @@
1
+ module Dev
2
+ class Jira
3
+ class Histories
4
+ def self.populate(data)
5
+ return nil unless data.attrs.key?('changelog')
6
+
7
+ data.changelog['histories'].map { |it| Jira::History.new(it) }
8
+ end
9
+ end
10
+ end
11
+ 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
@@ -6,6 +6,6 @@ module Dev
6
6
  # Use 'v.v.v.pre.alpha.v' for pre-release vesions
7
7
  # Use 'v.v.v.beta.v for beta versions
8
8
  # Use semantic versioning for any releases (https://semver.org/)
9
- VERSION = '2.1.6.pre.alpha.2'.freeze
9
+ VERSION = '2.1.7.pre.alpha.1'.freeze
10
10
  end
11
11
  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.6.pre.alpha.2
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-05 00:00:00.000000000 Z
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