firespring_dev_commands 2.1.6.pre.alpha.2 → 2.1.7.pre.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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